Skip to content

Commit

Permalink
feat: add next plugin that deals with webpack workarounds
Browse files Browse the repository at this point in the history
The canvas package, which is a dependency of jsdom, has a native binding which causes the following error during Next.js build process: "Module did not self-register".
We circuvent that by using a null-loader.
See vercel/next.js#7894

The ws package, which is also a dependency of jsdom, tries to optionally load some dependencies.
This produces a warning in webpack that we want to avoid.
We circuvent that by adding them to externals.
  • Loading branch information
satazor committed Nov 22, 2019
1 parent adc8690 commit 9862f65
Show file tree
Hide file tree
Showing 6 changed files with 173 additions and 8 deletions.
18 changes: 14 additions & 4 deletions README.md
Expand Up @@ -29,7 +29,17 @@ All the polyfilling will be taken care by this library automatically, so that yo
## Setup

#### 1. Create a root folder named `intl` with the following structure:
#### 1 Add the plugin to your `next.config.js`

```js
const withNextIntl = require('@moxy/next-intl/plugin');

module.exports = withNextIntl()({ ...nextConfig });
```

This plugin will make some [modifications](src/plugin.js) to your webpack config to circuvent a few issues related to JSDOM, which is a runtime dependency of `react-intl` for the server.

#### 2. Create a root folder named `intl` with the following structure:

```
intl/
Expand Down Expand Up @@ -71,7 +81,7 @@ The `messages/en-US.json` file contains the messages for the `en-US` locale:
}
```

#### 2. Include `<NextIntlScript>` in `pages/_document.js`:
#### 3. Include `<NextIntlScript>` in `pages/_document.js`:

```js
import React from 'react';
Expand All @@ -96,7 +106,7 @@ export default class MyDocument extends Document {
}
```

#### 3. Wrap your app with `withNextIntlSetup` in `pages/_app.js`:
#### 4. Wrap your app with `withNextIntlSetup` in `pages/_app.js`:

```js
import React from 'react';
Expand Down Expand Up @@ -131,7 +141,7 @@ class MyApp extends App {
export default withNextIntlSetup(nextIntlConfig)(MyApp);
```

#### 4. Ready!
#### 5. Ready!

You may now use [`react-intl`](https://www.npmjs.com/package/react-intl) as you normally would. Moreover, you will receive the current locale in your pages' `getInitialProps` static function.

Expand Down
70 changes: 67 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -50,6 +50,7 @@
"hoist-non-react-statics": "^3.3.1",
"jsdom": "^15.2.1",
"memoize-one": "^5.1.1",
"null-loader": "^3.0.0",
"p-cancelable": "^2.0.0",
"pico-signals": "^1.0.0",
"prop-types": "^15.7.2",
Expand All @@ -66,7 +67,6 @@
"@commitlint/config-conventional": "^8.1.0",
"@moxy/jest-config": "^1.1.0",
"@testing-library/react": "^9.3.2",
"babel-jest": "^24.9.0",
"babel-preset-moxy": "^3.2.0",
"delay": "^4.3.0",
"eslint": "^6.6.0",
Expand Down
3 changes: 3 additions & 0 deletions plugin.js
@@ -0,0 +1,3 @@
/* eslint-disable prefer-import/prefer-import-over-require */

module.exports = require('./lib/plugin');
41 changes: 41 additions & 0 deletions src/plugin.js
@@ -0,0 +1,41 @@
const castArray = (value) => {
if (Array.isArray(value)) {
return value;
}

return value != null ? [value] : [];
};

const withNextIntl = () => (nextConfig = {}) => ({
...nextConfig,
webpack: (config, options) => {
const { isServer } = options;

if (isServer) {
// The canvas package, which is a dependency of jsdom, has a native binding which causes
// the following error during Next.js build process: "Module did not self-register"
// See https://github.com/zeit/next.js/issues/7894
// We circuvent that by using a null-loader
config.module.rules.unshift({
test: require.resolve('canvas'),
loader: require.resolve('null-loader'),
});

// The ws package, which is also a dependency of jsdom, tries to optionally load some dependencies
// This produces a warning in webpack that we want to avoid
config.externals = [
'bufferutil',
'utf-8-validate',
...castArray(config.externals),
];
}

if (typeof nextConfig.webpack === 'function') {
return nextConfig.webpack(config, options);
}

return config;
},
});

export default withNextIntl;
47 changes: 47 additions & 0 deletions src/plugin.test.js
@@ -0,0 +1,47 @@
const nextIntlPlugin = require('./plugin');

const webpackOptions = {
isServer: true,
};

const createWebpackConfig = () => ({
module: {
rules: [
{
test: 'foo',
loader: 'foo-loader',
},
],
},
externals: () => {},
});

it('should add a rule for canvas that uses null-loader', () => {
const config = nextIntlPlugin()().webpack(createWebpackConfig(), webpackOptions);

const rule = config.module.rules[0];

expect(rule.test).toBe(require.resolve('canvas'));
expect(rule.loader).toBe(require.resolve('null-loader'));
});

it('should add no canvas rule when not server', () => {
const config = nextIntlPlugin()().webpack(createWebpackConfig(), { ...webpackOptions, isServer: false });

expect(config.module.rules[0].test).toBe('foo');
});

it('should add ws\'s optional dependencies to externals', () => {
const config = nextIntlPlugin()().webpack(createWebpackConfig(), webpackOptions);

expect(config.externals).toHaveLength(3);
expect(config.externals[0]).toBe('bufferutil');
expect(config.externals[1]).toBe('utf-8-validate');
expect(typeof config.externals[2]).toBe('function');
});

it('should leave externals untouched when not server', () => {
const config = nextIntlPlugin()().webpack(createWebpackConfig(), { ...webpackOptions, isServer: false });

expect(typeof config.externals).toBe('function');
});

0 comments on commit 9862f65

Please sign in to comment.