Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,4 @@ jobs:
with:
node-version: ${{ matrix.node }}
- run: npm install
- run: npm run test
- run: npm run release:check
33 changes: 23 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,30 @@ yarn add @hellotext/hellotext

### Configure

Import the library into your app.
Import the browser/bundler entry into your app.

```javascript
import Hellotext from '@hellotext/hellotext'
```

If you're running in a non-browser environment, such as Node.js, you can import the vanilla implementation which only includes
Hellotext.js class without initializing other libraries that rely on the browser environment.
The root entry is the browser pixel entry. It starts the Stimulus controllers used by Hellotext Forms and Webchat and assigns `window.Hellotext` for browser integrations.

If you're running in a non-browser environment, such as Node.js, you can import the vanilla implementation which only includes the Hellotext.js class without initializing other libraries that rely on the browser environment.

```javascript
import Hellotext from '@hellotext/hellotext/vanilla'
```

Use the vanilla entry for Node.js, SSR, tests, and other environments where `window` or `document` may not exist.

Supported public entrypoints are:

- `@hellotext/hellotext`
- `@hellotext/hellotext/vanilla`
- `@hellotext/hellotext/styles/index.css`

Internal `src` and `lib` paths are not public API and may change between releases.

Initialize the library passing the public `HELLOTEXT_BUSINESS_ID` identifier that represents the business.

You can find it from the business's settings page.
Expand Down Expand Up @@ -74,13 +85,15 @@ import '@hellotext/hellotext/styles/index.css'

## For Script Tag Users

The UMD bundle (`dist/hellotext.js`) includes the CSS automatically:
The UMD bundle (`dist/hellotext.js`) is the stable script-tag/CDN/GTM artifact and includes the CSS automatically:

```html
<script src="https://unpkg.com/@hellotext/hellotext"></script>
<!-- CSS is included in the bundle -->
```

The package also ships `dist/hellotext.umd.js` as the explicit UMD artifact name. In this release, `dist/hellotext.js` is kept as the compatibility alias and remains the path used by package metadata.

## Events

This library emits events that you can listen to and perform specific action when the event happens.
Expand Down Expand Up @@ -117,9 +130,9 @@ Hellotext.initialize('HELLOTEXT_BUSINESS_ID', configurationOptions)

#### Configuration Options

| Property | Description | Type | Default |
| ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | ----------------------------------------- |
| session | A valid Hellotext session which was stored previously. When not set, Hellotext attempts to retrieve the stored value from `document.cookie` when available, otherwise it creates a new session. | String | null |
| autoGenerateSession | Whether the library should automatically generate a session when no session is found in the query or the cookies | Boolean | true |
| forms | An object that controls how Hellotext should control the forms on the page. See [Forms](/docs/forms.md) documentation for more information. | Object | { autoMount: true, successMessage: true } |
| webchat | An object that overrides the dashboard webchat configuration, or `false` to disable automatic webchat mounting. See [Webchat](/docs/webchat.md). | Object \| false | Dashboard webchat when configured |
| Property | Description | Type | Default |
| ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- | ----------------------------------------- |
| session | A valid Hellotext session which was stored previously. When not set, Hellotext attempts to retrieve the stored value from `document.cookie` when available, otherwise it creates a new session. | String | null |
| autoGenerateSession | Whether the library should automatically generate a session when no session is found in the query or the cookies | Boolean | true |
| forms | An object that controls how Hellotext should control the forms on the page. See [Forms](/docs/forms.md) documentation for more information. | Object | { autoMount: true, successMessage: true } |
| webchat | An object that overrides the dashboard webchat configuration, or `false` to disable automatic webchat mounting. See [Webchat](/docs/webchat.md). | Object \| false | Dashboard webchat when configured |
1 change: 1 addition & 0 deletions dist/hellotext.umd.js

Large diffs are not rendered by default.

197 changes: 197 additions & 0 deletions docs/packaging.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
# Packaging

This document explains the package layout and compatibility decisions made while implementing issue #51.

## Goals

- Keep the public browser pixel stable for current production users.
- Fix ESM and CommonJS package resolution.
- Preserve CDN, GTM, and script-tag compatibility.
- Keep Node/SSR usage available through the vanilla entrypoint.
- Verify the packed npm artifact before publishing.

## Public Entrypoints

Supported public imports are:

```js
import Hellotext from '@hellotext/hellotext'
import Hellotext from '@hellotext/hellotext/vanilla'
import '@hellotext/hellotext/styles/index.css'
```

The package also exposes:

```js
require('@hellotext/hellotext/package.json')
```

Internal paths under `src` or `lib` are not public API. They may remain in the package for compatibility and debugging, but consumers should not rely on them.

## Root Browser Entry

`@hellotext/hellotext` is the browser-oriented entrypoint.

It intentionally:

- Starts Stimulus controllers.
- Registers Hellotext form, webchat, emoji, and message controllers.
- Assigns `window.Hellotext`.

This behavior is preserved because the primary use case is dropping Hellotext.js into a browser environment and having everything set up automatically.

## Vanilla Entry

`@hellotext/hellotext/vanilla` is the Node, SSR, test, and sandbox-safe entrypoint.

It imports the core Hellotext class without starting browser integrations or touching `window` at import time. This path is kept for environments such as Shopify Pixel sandboxes and other non-browser runtimes.

## Build Output

The package now separates ESM and CommonJS outputs:

```text
lib/
esm/
package.json
index.js
vanilla.js
...
cjs/
index.cjs
vanilla.cjs
...
dist/
hellotext.js
hellotext.umd.js
styles/
index.css
index.d.ts
```

`lib/esm/package.json` declares `"type": "module"`, allowing ESM `.js` files to load correctly without changing the package-level module type.

Generated ESM imports use explicit file extensions, for example:

```js
import Hellotext from './hellotext.js'
import { Configuration } from './core/index.js'
```

Generated CommonJS requires use explicit `.cjs` paths, for example:

```js
require('./hellotext.cjs')
require('./core/index.cjs')
```

This avoids ambiguous resolution when `.js` and `.cjs` builds exist in the package.

## UMD Artifacts

The explicit UMD artifact is:

```text
dist/hellotext.umd.js
```

The legacy CDN/GTM/script-tag artifact remains:

```text
dist/hellotext.js
```

`dist/hellotext.js` is a compatibility alias of `dist/hellotext.umd.js`. Package metadata continues to point to `dist/hellotext.js` to avoid breaking existing integrations.

Current metadata intentionally remains:

```json
{
"browser": "./dist/hellotext.js",
"unpkg": "./dist/hellotext.js"
}
```

The release smoke test verifies both UMD files exist, are non-empty, and are identical.

## Exports Map

The package exports are intentionally limited to documented public paths:

```json
{
".": {
"types": "./index.d.ts",
"browser": "./dist/hellotext.js",
"import": "./lib/esm/index.js",
"require": "./lib/cjs/index.cjs",
"default": "./dist/hellotext.js"
},
"./vanilla": {
"types": "./index.d.ts",
"import": "./lib/esm/vanilla.js",
"require": "./lib/cjs/vanilla.cjs"
},
"./styles/index.css": "./styles/index.css",
"./package.json": "./package.json"
}
```

The broad `./styles/*` export is currently preserved for compatibility, but `./styles/index.css` is the documented CSS entrypoint.

## CSS Side Effects

CSS is side-effectful and must not be tree-shaken away. The root browser entries are also side-effectful because they start browser integrations.

The `sideEffects` field includes CSS, browser root entries, and the UMD bundle.

## Node Support

The supported Node floor is Node 20:

```json
{
"engines": {
"node": ">=20.0.0"
}
}
```

CI verifies Node 20 and Node 22.

## Release Verification

Publishing is guarded by:

```sh
npm run release:check
```

This runs:

- Unit tests.
- Clean build.
- Type declaration checks.
- Packed-package smoke tests.

The smoke test creates the npm tarball, installs it into temporary consumer projects, and verifies:

- `import '@hellotext/hellotext'` in a browser-like environment
- `require('@hellotext/hellotext')` in a browser-like environment
- `import '@hellotext/hellotext/vanilla'`
- `require('@hellotext/hellotext/vanilla')`
- `require('@hellotext/hellotext/package.json')`
- Bundling the root browser entry with `@hellotext/hellotext/styles/index.css`
- `dist/hellotext.js`
- `dist/hellotext.umd.js`
- UMD alias consistency

## Deferred Work

The following were intentionally left for future changes:

- Add source maps for `lib` and UMD bundles.
- Decide whether `dist/hellotext.js` should remain forever or become a documented legacy alias in a future major release.
- Revisit whether `src` should remain in the published package.
- Align Babel targets with the Node 20 support floor.
- Modernize ESM output for smaller bundles.
4 changes: 2 additions & 2 deletions lib/api/acks.cjs → lib/cjs/api/acks.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _hellotext = _interopRequireDefault(require("../hellotext"));
var _core = require("../core");
var _hellotext = _interopRequireDefault(require("../hellotext.cjs"));
var _core = require("../core/index.cjs");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor); } }
Expand Down
2 changes: 1 addition & 1 deletion lib/api/businesses.cjs → lib/cjs/api/businesses.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _core = require("../core");
var _core = require("../core/index.cjs");
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor); } }
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; }
Expand Down
6 changes: 3 additions & 3 deletions lib/api/events.cjs → lib/cjs/api/events.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _core = require("../core");
var _models = require("../models");
var _response = require("./response");
var _core = require("../core/index.cjs");
var _models = require("../models/index.cjs");
var _response = require("./response.cjs");
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor); } }
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; }
Expand Down
6 changes: 3 additions & 3 deletions lib/api/forms.cjs → lib/cjs/api/forms.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _core = require("../core");
var _hellotext = _interopRequireDefault(require("../hellotext"));
var _response = require("./response");
var _core = require("../core/index.cjs");
var _hellotext = _interopRequireDefault(require("../hellotext.cjs"));
var _response = require("./response.cjs");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor); } }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _hellotext = _interopRequireDefault(require("../hellotext"));
var _core = require("../core");
var _response = require("./response");
var _hellotext = _interopRequireDefault(require("../hellotext.cjs"));
var _core = require("../core/index.cjs");
var _response = require("./response.cjs");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor); } }
Expand Down
16 changes: 8 additions & 8 deletions lib/api/index.cjs → lib/cjs/api/index.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@ Object.defineProperty(exports, "Response", {
});
exports.default = void 0;
exports.keepaliveFor = keepaliveFor;
var _businesses = _interopRequireDefault(require("./businesses"));
var _events = _interopRequireDefault(require("./events"));
var _forms = _interopRequireDefault(require("./forms"));
var _identifications = _interopRequireDefault(require("./identifications"));
var _webchats = _interopRequireDefault(require("./webchats"));
var _acks = _interopRequireDefault(require("./acks"));
var _response = require("./response");
var _businesses = _interopRequireDefault(require("./businesses.cjs"));
var _events = _interopRequireDefault(require("./events.cjs"));
var _forms = _interopRequireDefault(require("./forms.cjs"));
var _identifications = _interopRequireDefault(require("./identifications.cjs"));
var _webchats = _interopRequireDefault(require("./webchats.cjs"));
var _acks = _interopRequireDefault(require("./acks.cjs"));
var _response = require("./response.cjs");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor); } }
Expand Down Expand Up @@ -81,4 +81,4 @@ let API = /*#__PURE__*/function () {
}]);
return API;
}();
exports.default = API;
exports.default = API;
File renamed without changes.
6 changes: 3 additions & 3 deletions lib/api/submissions.cjs → lib/cjs/api/submissions.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _hellotext = _interopRequireDefault(require("../hellotext"));
var _core = require("../core");
var _response = require("./response");
var _hellotext = _interopRequireDefault(require("../hellotext.cjs"));
var _core = require("../core/index.cjs");
var _response = require("./response.cjs");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor); } }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _core = require("../../core");
var _hellotext = _interopRequireDefault(require("../../hellotext"));
var _response = require("../response");
var _core = require("../../core/index.cjs");
var _hellotext = _interopRequireDefault(require("../../hellotext.cjs"));
var _response = require("../response.cjs");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor); } }
Expand Down
Loading