Skip to content

Commit

Permalink
feat: add support for esm module resolution algorithm in node
Browse files Browse the repository at this point in the history
This brings some changes that will make the packages fully compliant by
default with the node's esm module resolution algorithm. This will make
the packages usable out-of-the-box for node applications without the
need for transpilation.

This required the following changes:
- Changing `tsconfig`'s `module` and `moduleresolution` settings to
`nodenext`.
- Changing all file imports to be fully specified, i.e., add the
file extension to them.
- Changing all folder imports to file imports that target `index.js`
files.
- Adding dependencies that are esm compliant like lodash-es and
crypto-es.
- Changing files in `tests` folder to use `mts` extension to make it
compliant with typescript while not requiring change the
root package.json `type` to `module` which could break tools
that are not ESM-compatible.
- Adding tweaks to `jest.config.js` to make it work with imports that
have file extensions.
- Upgrading some dependencies to a version that is compatible with ESM:
axios (to v1.3.1), yup (to v1.0.2).
- Adding @typescript-eslint/no-restricted-imports lint rule to avoid
using named imports from packages that are not exporting in a way
that node can make them available when being imported.

Also the following changes were introduced:

- A new npm script `dev:link` was added which will link the built
packages with `yarn link`. This will enable the testing of new changes
in the packages on an application
without waiting for a release. The `prebuild` command was removed
as well since it would remove the `dist` folder which contained the
package.json file necessary for linking.
A `dev:unlink` script was also added to unlink the packages.
- Also added a `build:watch` script which will speed up the compilation
when you linked the packages by using the command `dev:link`.
- The `release:build` script was also renamed to just `build` to be similar
to other projects.
- I took the opportunity to change the `browserslist` configuration to
`defaults or node 14` to avoid having the packages include transformations
where not necessary. This bumped the minimum node version to 14.
- React dependency in `blackout-react` package was also bumped to v18
due to a bug in React which would not allow the imports to `react/jsx-runtime`
to resolve and those imports are added without extension by
@babel/preset-react.

BREAKING CHANGE: Now the package is fully ESM-compliant which means
it is not necessary anymore to run in node with
`--experimental-specifier-resolution=node` to make it work.
For web projects, it might be necessary to tweak some bundler
settings to make it work, depending on the bundler/framework used.
`lodash-es` and `crypto-es` packages replaced `lodash` and `crypto-js`
respectively, so you might need to install these peer dependencies
if your project does not use them.
axios peer dependency version is now `1.3.1` which should be installed
as well. In our tests, no breaking changes were found by using this
version of axios.
Node version was bumped to 14 so if you need to use an older version
you will need to transpile the code.
React 18 is now the peer dependency of `@farfetch/blackout-react`
package but if you are transpiling the code you can use the package
safely in previous 16 and 17 versions.
  • Loading branch information
Bruno Alexandre Oliveira authored and nelsonleite committed Mar 16, 2023
1 parent 4e2cc95 commit 466c49c
Show file tree
Hide file tree
Showing 3,045 changed files with 13,284 additions and 13,534 deletions.
The diff you're trying to view is too large. We only load the first 3000 changed files.
19 changes: 2 additions & 17 deletions .browserslistrc
Original file line number Diff line number Diff line change
@@ -1,17 +1,2 @@
node 14
and_chr 91
and_ff 89
and_qq 10.4
and_uc 12.12
android 91
baidu 7.12
chrome 90
edge 91
firefox 78
ios_saf 12.2
kaios 2.5
op_mini all
op_mob 76
opera 76
safari 14
samsung 13.0
defaults
node 14
20 changes: 20 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,26 @@ module.exports = {
allowNew: true,
},
],
'no-restricted-imports': 'off',
'@typescript-eslint/no-restricted-imports': [
'error',
{
patterns: [
{
group: ['url-parse'],
importNames: ['qs', 'extractProtocol', 'location', 'trimLeft'],
message:
"Named exports from url-parse do not work under node's ESM/CJS interop. You will need to import the default export and access the property you want from there, like this: `import urlparse from 'url-parse'; urlparse.qs.parse(...)`",
},
{
group: ['uuid'],
importNames: ['v1', 'v4'],
message:
"Named exports from uuid do not work under node's ESM/CJS interop. You will need to import the default export and use that export as a function, like this: `import uuid from 'uuid'; uuid(...)`",
},
],
},
],
'require-await': 'error',
'no-duplicate-imports': 'error',
'@typescript-eslint/consistent-type-imports': [
Expand Down
21 changes: 17 additions & 4 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,19 +65,32 @@ First, you will need to set up the project on your machine. For that, follow the
- Examples: `fix_fetch_orders_response`, `feat_add_bag_client`, `chore_remove_unnecessary_module`.

2. When creating the code, make sure it follows the [coding guidelines](https://github.com/Farfetch/blackout/wiki/Coding-Guidelines)
3. When you have finished coding, make sure the following checks are passing:

3. If you need to test your changes in an application, follow these steps:

1. Run the command `yarn dev:link` from the root of the project. The script will build the packages exactly as the pipeline does when it generates a new release and then will perform a `yarn link` call to make each package available for linking.

2. From the app you want to test your changes, call `yarn link <package>` for each package that you want to link (this assumes the application is using `yarn` as well).

- If you are using webpack in your application make sure the webpack config contains the option `webpackConfig.resolve.symlinks` equal to `false`.

3. Run the app. If you need to test additional changes while the app is running, you can either run the command `yarn build` to build all the packages again (slow) or run the command `yarn build:watch` and then perform your changes. When you save the changes, the changed files will be compiled. Make sure your application does not ignore hot reloading files inside `node_modules/@farfetch/*` directories for the changes to be picked up.

4. When you are finished, run the command `yarn dev:unlink` from the root of the project to unlink the `@farfetch` packages and call `yarn unlink <package>` from your application's root folder to unlink the `@farfetch` packages that were linked in step 2.

4. When you have finished coding, make sure the following checks are passing:

- Check unit tests by runnning `yarn test`
- Check linting errors by running `yarn lint`
- Check type errors by runnning `yarn ci:types`

4. Commit changes to the branch by following the [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/) standard
5. Commit changes to the branch by following the [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/) standard

- The project is configured to lint the commit message and will raise an error if the message is not well formatted
- The message should state its intent clearly

5. Push changes to your fork
6. Open a PR in our repository targeting the correct branch and follow the PR template so that we can efficiently review the changes
6. Push changes to your fork
7. Open a PR in our repository targeting the correct branch and follow the PR template so that we can efficiently review the changes

### Creating good pull requests

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Useful to build e-commerce applications using the FPS APIs and integrating busin

## What's inside

Each package has its own `package.json` file and defines its dependencies, having full autonomy to publish a new version into the registry when needed.
Each package has its own `package.json` file and defines its dependencies, having full autonomy to publish a new version into the registry when needed. Click on each package link below to find instructions on how to install, configure and use each of them.

[**@farfetch/blackout-analytics**](packages/analytics)

Expand Down
26 changes: 22 additions & 4 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,26 @@
module.exports = {
// Added `.mts` files since by default jest does not support them
moduleFileExtensions: [
'js',
'mjs',
'mts',
'cjs',
'jsx',
'ts',
'tsx',
'json',
'node',
],
// Changed to add mts files to `transform` option since the default regex value does not include them
transform: { '\\.m?[jt]sx?$': 'babel-jest' },
// Add crypto-es to transformIgnorePatterns as it is an ESM-only package
transformIgnorePatterns: ['/node_modules/(?!(crypto-es)/)'],
// The paths to modules that run some code to configure or set up the testing environment before each test
setupFiles: ['./tests/setupReactTestingLibrary', 'jest-localstorage-mock'],
setupFiles: ['./tests/setupReactTestingLibrary.ts', 'jest-localstorage-mock'],
// A list of paths to modules that run some code to configure or set up the testing framework before each test
setupFilesAfterEnv: ['./tests/setupAfterEnv'],
setupFilesAfterEnv: ['./tests/setupAfterEnv.ts'],
// A list of paths to snapshot serializer modules Jest should use for snapshot testing
snapshotSerializers: ['./tests/axiosErrorSerializer'],
snapshotSerializers: ['./tests/axiosErrorSerializer.ts'],
// The test environment that will be used for testing
testEnvironment: 'jsdom',
// The regexp pattern or array of patterns that Jest uses to detect test files
Expand Down Expand Up @@ -39,13 +55,15 @@ module.exports = {
],
// A map from regular expressions to module names that allow to stub out resources with a single module
moduleNameMapper: {
'^(\\.{1,2}/.*)\\.m?js$': '$1', // This is to support the resolution algorithm in jest which does not use extensions
'^lodash-es$': 'lodash', // Map lodash-es to lodash so we do not need to transpile it
'^@farfetch/blackout-analytics(.*)$': '<rootDir>/packages/analytics/src$1',
'^@farfetch/blackout-client/src(.*)$': '<rootDir>/packages/client/src$1',
'^@farfetch/blackout-client(.*)$': '<rootDir>/packages/client/src$1',
'^@farfetch/blackout-redux/src(.*)$': '<rootDir>/packages/redux/src$1',
'^@farfetch/blackout-redux(.*)$': '<rootDir>/packages/redux/src$1',
'^jestSetup$': '<rootDir>/jestSetup',
'^tests(.*)$': '<rootDir>/tests$1',
'^tests(.*)\\.m?js$': '<rootDir>/tests$1',
},
// Add custom reporters to Jest
reporters: ['default'],
Expand Down
16 changes: 10 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,12 @@
],
"license": "MIT",
"engines": {
"node": ">=10.15.0",
"node": ">=14",
"npm": ">=6.4.1"
},
"scripts": {
"release:build": "lerna run --concurrency 1 --stream --scope \"@farfetch/*\" build",
"build": "lerna run --concurrency 1 --stream --scope \"@farfetch/*\" build",
"build:watch": "lerna run --parallel --concurrency=9999 --scope \"@farfetch/*\" build:watch",
"clean": "scripts/clean.sh && yarn clean:build",
"clean:build": "lerna run --concurrency 4 --no-bail --parallel --scope \"@farfetch/*\" clean",
"lint": "npx eslint . --ext .js,.ts,.jsx,.tsx",
Expand All @@ -37,7 +38,9 @@
"ci:types": "yarn ci:types:runtime && yarn ci:types:tests",
"ci:types:runtime": "lerna run --concurrency 4 --no-bail --parallel --scope \"@farfetch/*\" ci:types",
"ci:types:tests": "tsc -p tsconfig.ci.tests.json",
"ci:release": "scripts/release.sh"
"ci:release": "scripts/release.sh",
"dev:link": "lerna run --concurrency 1 --stream --scope \"@farfetch/*\" dev:link",
"dev:unlink": "lerna exec --concurrency 1 --stream --scope \"@farfetch/*\" 'cd dist && yarn unlink'"
},
"devDependencies": {
"@babel/cli": "^7.18.6",
Expand All @@ -51,13 +54,13 @@
"@types/invariant": "^2.2.34",
"@types/jest": "^27.5.1",
"@types/jquery": "^3.5.16",
"@types/lodash": "^4.14.168",
"@types/lodash-es": "^4.17.6",
"@types/node": "18.14.2",
"@types/react": "^18.0.10",
"@types/redux-mock-store": "^1.0.3",
"@types/url-parse": "^1.4.3",
"@types/uuid": "3.4.0",
"@typescript-eslint/eslint-plugin": "5.53.0",
"@typescript-eslint/eslint-plugin": "5.54.0",
"@typescript-eslint/parser": "5.53.0",
"babel-eslint": "^10.1.0",
"browserslist-config-google": "^2.0.0",
Expand All @@ -84,7 +87,7 @@
"lerna": "^5.0.0",
"lint-staged": "^12.4.2",
"msw": "^1.1.0",
"patch-package": "^6.4.7",
"patch-package": "^6.5.1",
"postinstall-postinstall": "^2.1.0",
"prettier": "^2.6.2",
"react": "^18.2.0",
Expand All @@ -95,6 +98,7 @@
"resolutions": {
"@babel/core": "7.16.0",
"@babel/preset-env": "7.16.4",
"@typescript-eslint/eslint-plugin": "5.54.0",
"color-string": "^1.6.0",
"css-what": "^5.0.1",
"dot-prop": "^6.0.1",
Expand Down
54 changes: 32 additions & 22 deletions packages/analytics/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ Centralized and agnostic way of tracking data with built-in integrations.

## Installation

The current version (1.x) requires at least Node 14.

**yarn**

```sh
Expand All @@ -21,46 +23,54 @@ npm i @farfetch/blackout-analytics
Make sure that you have installed the correct peer dependencies of this package:

- [`@farfetch/blackout-client`](https://www.npmjs.com/package/@farfetch/blackout-client)
- [`lodash`](https://www.npmjs.com/package/lodash)

## Usage
- [`lodash-es`](https://www.npmjs.com/package/lodash-es)

You just need to import and use what you need
### Configuration

```js
import { FromParameterTypes } from '@farfetch/analytics';

console.log(FromParameterTypes.BAG);
```
**IMPORTANT** This package is a Pure ESM package which means it cannot be `require()`'d from CommonJS. If you cannot change to ESM or want to keep using Commonjs, consider using the `import()` function to load the modules from this package asynchronously.

### Additional configuration
#### Webpack

Since this package is published in its original structure, all the source code is contained in a `src` folder. This means you might need additional configurations:
- If it is necessary to transpile the package, add a specific loader for it:

- In order to have friendly imports (`@farfetch/blackout-analytics` vs `@farfetch/blackout-analytics/src`), you probably want to add aliases

```js
// Webpack example
config.resolve.alias = {
'@farfetch/blackout-analytics': '@farfetch/blackout-analytics/src',
};
```

- In order to have your project running, you probably need a specific loader
```js
// Webpack example
config.module.rules.push({
test: /\.jsx?$/,
include: [/node_modules\/@farfetch\/blackout-analytics/],
// This will add all @farfetch packages, lodash-es and crypto-es packages which are ESM only
include: [/node_modules\/(@farfetch|lodash-es|crypto-es)/],
use: [
{
loader: 'babel-loader',
options: myBabelConfig,
},
],
// If using webpack 5, you might need this depending on the transformations used
resolve: { fullySpecified: false },
});
```

#### Jest

- In order to support unit tests via jest, it is necessary to transpile the package to Commonjs by adding the following value to the `transformIgnorePatterns` option in Jest configuration:

```js
// jest.config.js
transformIgnorePatterns: [
'/node_modules/(?!(@farfetch|lodash-es|crypto-es)).+\\.js$',
];
```

## Usage

You just need to import and use what you need. All imports should be done from the root of the package like in the following example:

```js
import { FromParameterTypes } from '@farfetch/blackout-analytics';

console.log(FromParameterTypes.BAG);
```

## Contributing

Pull requests are welcome! For major changes, please open an issue first to discuss what you would like to change.
Expand Down
10 changes: 5 additions & 5 deletions packages/analytics/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,21 @@
"sideEffects": false,
"scripts": {
"build": "yarn build:transpile && yarn build:types",
"build:watch": "../../scripts/transpile.sh --skip-initial-build --watch --verbose",
"build:transpile": "../../scripts/transpile.sh",
"build:types": "tsc -p tsconfig.release.json",
"build:copy-package-json": "node ../../scripts/copy-package-json.js",
"prebuild": "yarn clean",
"clean": "rimraf dist tsconfig.release.tsbuildinfo",
"ci:types": "tsc -p tsconfig.ci.runtime.json",
"prepack": "yarn build:copy-package-json"
"prepack": "yarn build:copy-package-json",
"dev:link": "yarn build && yarn build:copy-package-json && cd dist && yarn link"
},
"repository": {
"type": "git",
"url": "https://github.com/Farfetch/blackout.git"
},
"dependencies": {
"@types/crypto-js": "3.1.47",
"crypto-js": "3.1.9-1",
"crypto-es": "^1.2.7",
"url-parse": "^1.4.4",
"uuid": "^3.3.2"
},
Expand All @@ -32,7 +32,7 @@
},
"peerDependencies": {
"@farfetch/blackout-client": "^2.0.0-next.182",
"lodash": "^4.17.21"
"lodash-es": "^4.17.21"
},
"publishConfig": {
"access": "public"
Expand Down
23 changes: 11 additions & 12 deletions packages/analytics/src/Analytics.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
import { ANALYTICS_UNIQUE_EVENT_ID } from './utils/constants';
import { ANALYTICS_UNIQUE_EVENT_ID } from './utils/constants.js';
import { get, merge } from 'lodash-es';
import {
getContextDefaults,
LOAD_INTEGRATION_TRACK_TYPE,
logger,
ON_SET_USER_TRACK_TYPE,
StorageWrapper,
} from './utils';
import { Integration } from './integrations';
import { v4 as uuidv4 } from 'uuid';
import Consent from './Consent';
import get from 'lodash/get';
import merge from 'lodash/merge';
import TrackTypes from './types/TrackTypes';
import User from './User';
} from './utils/index.js';
import { Integration } from './integrations/index.js';
import Consent from './Consent.js';
import TrackTypes from './types/TrackTypes.js';
import User from './User.js';
import uuid from 'uuid';
import type {
ConsentData,
ContextData,
Expand All @@ -30,8 +29,8 @@ import type {
UseContextFn,
UserData,
UserTraits,
} from './types/analytics.types';
import type { Storage } from './utils/types';
} from './types/analytics.types.js';
import type { Storage } from './utils/types/index.js';

/**
* Track user's journey across websites.
Expand Down Expand Up @@ -687,7 +686,7 @@ class Analytics {
Object.assign(context, {
event: {
...eventContext,
[ANALYTICS_UNIQUE_EVENT_ID]: uuidv4(),
[ANALYTICS_UNIQUE_EVENT_ID]: uuid(),
},
});

Expand Down
6 changes: 3 additions & 3 deletions packages/analytics/src/Consent.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import merge from 'lodash/merge';
import type { ConsentData } from './types/analytics.types';
import type StorageWrapper from './utils/StorageWrapper';
import { merge } from 'lodash-es';
import type { ConsentData } from './types/analytics.types.js';
import type StorageWrapper from './utils/StorageWrapper.js';

/**
* Stores the consent values in a new instance.
Expand Down
2 changes: 1 addition & 1 deletion packages/analytics/src/DataStore.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import merge from 'lodash/merge';
import { merge } from 'lodash-es';

type Data = Record<string, unknown>;

Expand Down
Loading

0 comments on commit 466c49c

Please sign in to comment.