Skip to content

Commit fa44f95

Browse files
satazorivolimasilva
authored andcommitted
feat: add next plugin that deals with webpack workarounds (#4)
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.
1 parent adc8690 commit fa44f95

File tree

6 files changed

+198
-8
lines changed

6 files changed

+198
-8
lines changed

README.md

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,17 @@ All the polyfilling will be taken care by this library automatically, so that yo
2929
3030
## Setup
3131

32-
#### 1. Create a root folder named `intl` with the following structure:
32+
#### 1 Add the plugin to your `next.config.js`
33+
34+
```js
35+
const withNextIntl = require('@moxy/next-intl/plugin');
36+
37+
module.exports = withNextIntl()({ ...nextConfig });
38+
```
39+
40+
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.
41+
42+
#### 2. Create a root folder named `intl` with the following structure:
3343

3444
```
3545
intl/
@@ -71,7 +81,7 @@ The `messages/en-US.json` file contains the messages for the `en-US` locale:
7181
}
7282
```
7383

74-
#### 2. Include `<NextIntlScript>` in `pages/_document.js`:
84+
#### 3. Include `<NextIntlScript>` in `pages/_document.js`:
7585

7686
```js
7787
import React from 'react';
@@ -96,7 +106,7 @@ export default class MyDocument extends Document {
96106
}
97107
```
98108

99-
#### 3. Wrap your app with `withNextIntlSetup` in `pages/_app.js`:
109+
#### 4. Wrap your app with `withNextIntlSetup` in `pages/_app.js`:
100110

101111
```js
102112
import React from 'react';
@@ -131,7 +141,7 @@ class MyApp extends App {
131141
export default withNextIntlSetup(nextIntlConfig)(MyApp);
132142
```
133143

134-
#### 4. Ready!
144+
#### 5. Ready!
135145

136146
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.
137147

package-lock.json

Lines changed: 67 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
"hoist-non-react-statics": "^3.3.1",
5151
"jsdom": "^15.2.1",
5252
"memoize-one": "^5.1.1",
53+
"null-loader": "^3.0.0",
5354
"p-cancelable": "^2.0.0",
5455
"pico-signals": "^1.0.0",
5556
"prop-types": "^15.7.2",
@@ -66,7 +67,6 @@
6667
"@commitlint/config-conventional": "^8.1.0",
6768
"@moxy/jest-config": "^1.1.0",
6869
"@testing-library/react": "^9.3.2",
69-
"babel-jest": "^24.9.0",
7070
"babel-preset-moxy": "^3.2.0",
7171
"delay": "^4.3.0",
7272
"eslint": "^6.6.0",

plugin.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
/* eslint-disable prefer-import/prefer-import-over-require */
2+
3+
module.exports = require('./lib/plugin');

src/plugin.js

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
const castArray = (value) => {
2+
if (Array.isArray(value)) {
3+
return value;
4+
}
5+
6+
return value != null ? [value] : [];
7+
};
8+
9+
const withNextIntl = () => (nextConfig = {}) => ({
10+
...nextConfig,
11+
webpack: (config, options) => {
12+
const { isServer } = options;
13+
14+
if (isServer) {
15+
// The canvas package, which is a dependency of jsdom, has a native binding which causes
16+
// the following error during Next.js build process: "Module did not self-register"
17+
// See https://github.com/zeit/next.js/issues/7894
18+
// We circuvent that by using a null-loader
19+
config.module.rules.unshift({
20+
test: require.resolve('canvas'),
21+
loader: require.resolve('null-loader'),
22+
});
23+
24+
// The ws package, which is also a dependency of jsdom, tries to optionally load some dependencies
25+
// This produces a warning in webpack that we want to avoid
26+
config.externals = [
27+
'bufferutil',
28+
'utf-8-validate',
29+
...castArray(config.externals),
30+
];
31+
}
32+
33+
if (typeof nextConfig.webpack === 'function') {
34+
return nextConfig.webpack(config, options);
35+
}
36+
37+
return config;
38+
},
39+
});
40+
41+
export default withNextIntl;

src/plugin.test.js

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
const nextIntlPlugin = require('./plugin');
2+
3+
const webpackOptions = {
4+
isServer: true,
5+
};
6+
7+
const createWebpackConfig = () => ({
8+
module: {
9+
rules: [
10+
{
11+
test: 'foo',
12+
loader: 'foo-loader',
13+
},
14+
],
15+
},
16+
externals: () => {},
17+
});
18+
19+
it('should add a rule for canvas that uses null-loader', () => {
20+
const config = nextIntlPlugin()().webpack(createWebpackConfig(), webpackOptions);
21+
22+
const rule = config.module.rules[0];
23+
24+
expect(rule.test).toBe(require.resolve('canvas'));
25+
expect(rule.loader).toBe(require.resolve('null-loader'));
26+
});
27+
28+
it('should add no canvas rule when not server', () => {
29+
const config = nextIntlPlugin()().webpack(createWebpackConfig(), { ...webpackOptions, isServer: false });
30+
31+
expect(config.module.rules[0].test).toBe('foo');
32+
});
33+
34+
it('should add ws\'s optional dependencies to externals', () => {
35+
const config = nextIntlPlugin()().webpack(createWebpackConfig(), webpackOptions);
36+
37+
expect(config.externals).toHaveLength(3);
38+
expect(config.externals[0]).toBe('bufferutil');
39+
expect(config.externals[1]).toBe('utf-8-validate');
40+
expect(typeof config.externals[2]).toBe('function');
41+
});
42+
43+
it('should add ws\'s optional dependencies to externals (already an array)', () => {
44+
const originalConfig = {
45+
...createWebpackConfig(),
46+
externals: [() => {}],
47+
};
48+
49+
const config = nextIntlPlugin()().webpack(originalConfig, webpackOptions);
50+
51+
expect(config.externals).toHaveLength(3);
52+
expect(config.externals[0]).toBe('bufferutil');
53+
expect(config.externals[1]).toBe('utf-8-validate');
54+
expect(typeof config.externals[2]).toBe('function');
55+
});
56+
57+
it('should leave externals untouched when not server', () => {
58+
const config = nextIntlPlugin()().webpack(createWebpackConfig(), { ...webpackOptions, isServer: false });
59+
60+
expect(typeof config.externals).toBe('function');
61+
});
62+
63+
it('should call nextConfig webpack if defined', () => {
64+
const nextConfig = {
65+
webpack: jest.fn(() => 'foo'),
66+
};
67+
68+
const config = nextIntlPlugin()(nextConfig).webpack(createWebpackConfig(), webpackOptions);
69+
70+
expect(nextConfig.webpack).toHaveBeenCalledTimes(1);
71+
expect(config).toBe('foo');
72+
});

0 commit comments

Comments
 (0)