UNPKG

11.7 kBMarkdownView Raw
1# Next.js + Transpile `node_modules`
2
3![Build Status](https://github.com/martpie/next-transpile-modules/workflows/tests/badge.svg)
4![Dependencies](https://img.shields.io/david/martpie/next-transpile-modules)
5[![sponsor: Creative Tim](https://img.shields.io/badge/sponsor-Creative%20Tim-blue)](https://creative-tim.com/?affiliate_id=140482)
6
7Transpile modules from `node_modules` using the Next.js Babel configuration.
8
9Makes it easy to have local libraries and keep a slick, manageable dev experience.
10
11- Supports transpilation of all extensions supported by Next.js: `.js`, `.jsx`, `.ts`, `.tsx`, `.mjs`, `.css`, `.scss` and `.sass`
12- Enable hot-reloading on local packages
13- Most setups should work out of the box (npm, yarn, pnpm, ...)
14
15## What problems does it solve?
16
17This plugin aims to solve the following challenges:
18
19- code transpilation from local packages (think: a monorepo with a `styleguide` package)
20- code transpilation from NPM modules using ES6 imports (e.g `lodash-es`)
21
22What this plugin **does not aim** to solve:
23
24- any-package IE11-compatible maker
25
26## Compatibility table
27
28| Next.js version | Plugin version |
29| ----------------- | -------------- |
30| Next.js 9.5+ / 10 | 4.x, 5.x, 6.x |
31| Next.js 9.2 | 3.x |
32| Next.js 8 / 9 | 2.x |
33| Next.js 6 / 7 | 1.x |
34
35Latest Next.js version tested: **10.1.3**.
36
37## Installation
38
39```
40npm install --save next-transpile-modules
41```
42
43or
44
45```
46yarn add next-transpile-modules
47```
48
49## Usage
50
51### withTM(transpileModules [, options])
52
53- `transpileModules` String[]: modules to be transpiled
54- `options` Object (optional)
55 - `resolveSymlinks` Boolean: Enable symlinks resolution to their real path by Webpack (default to `false`)
56 - `debug` Boolean: Display some informative logs in the console (can get noisy!) (default to `false`)
57 - `__unstable_matcher` (path) => boolean: Custom matcher that will override the default one. Don't use it.
58
59#### Note on `resolveSymlinks`
60
61Node.js resolution is based on the fact that symlinks are resolved. Not resolving them will alter the behavior, but there are some cases where the alternative behavior makes things a lot easier:
62
63- You are using `npm/yarn link` to link packages into node_modules.
64- You are using `npm` with `file:` dependencies that live outside of your project directory
65 - `npm` will create symlinks in this case. Yarn will copy instead.
66
67If this **doesn't** apply to your use case **you should set `resolveSymlinks: true`**, which results in the original behavior and **better performance**.
68
69The following thing will use symlinks, but work great with `resolveSymlinks: true`:
70
71- `pnpm`
72- `yarn` workspaces
73- `yarn 2` portals
74
75#### Note on Webpack 5 support
76
77Since `6.2.0` (with `next@10.0.6`), Webpack 5 support is automatically enabled via the `future.webpack5` flag, but is experimental and may break in any patch or minor release (from both `next` or `next-transpile-modules`) without any warning, be careful!
78
79#### Examples
80
81```js
82// next.config.js
83const withTM = require('next-transpile-modules')(['somemodule', 'and-another']); // pass the modules you would like to see transpiled
84
85module.exports = withTM();
86```
87
88```js
89// next.config.js
90const withTM = require('next-transpile-modules')(['somemodule', 'and-another']);
91
92module.exports = withTM({
93 future: {
94 webpack5: true,
95 },
96});
97```
98
99**Notes:**
100
101- please declare `withTM` as your last plugin (the "most nested" one).
102- make sure all your packages have [a valid `main` field](https://docs.npmjs.com/cli/v6/configuring-npm/package-json#main).
103- there is currently no way to transpile only parts of a package, it's all or nothing
104
105### Scoped packages
106
107You can include scoped packages or nested ones:
108
109```js
110const withTM = require('next-transpile-modules')(['@shared/ui', '@shared/utils']);
111
112// ...
113```
114
115```js
116const withTM = require('next-transpile-modules')(['styleguide/node_modules/lodash-es']);
117
118// ...
119```
120
121### With `next-compose-plugins`:
122
123```js
124const withPlugins = require('next-compose-plugins');
125const withTM = require('next-transpile-modules')(['some-module', 'and-another']);
126
127module.exports = withPlugins([withTM], {
128 // ...
129});
130```
131
132### CSS/SCSS support
133
134Since `next-transpile-modules@3` and `next@>9.2`, this plugin can also transpile CSS included in your transpiled packages. SCSS/SASS is also supported since `next-transpile-modules@3.1.0`.
135
136In your transpiled package:
137
138```js
139// shared-ui/components/Button.js
140import styles from './Button.module.css';
141
142function Button(props) {
143 return (
144 <button type='button' className={styles.error}>
145 {props.children}
146 </button>
147 );
148}
149
150export default Button;
151```
152
153```css
154/* shared-ui/components/Button.module.css */
155.error {
156 color: white;
157 background-color: red;
158}
159```
160
161In your app:
162
163```js
164// next.config.js
165const withTM = require('next-transpile-modules')(['shared-ui']);
166
167// ...
168```
169
170```jsx
171// pages/home.jsx
172import React from 'react';
173import Button from 'shared-ui/components/Button';
174
175const HomePage = () => {
176 return (
177 <main>
178 {/* will output <button class="Button_error__xxxxx"> */}
179 <Button>Styled button</Button>
180 </main>
181 );
182};
183
184export default HomePage;
185```
186
187It also supports global CSS import packages located in `node_modules`:
188
189```jsx
190// pages/_app.js
191import 'shared-ui/styles/global.css'; // will be imported globally
192
193export default function MyApp({ Component, pageProps }) {
194 return <Component {...pageProps} />;
195}
196```
197
198## FAQ
199
200### What is the difference with `@weco/next-plugin-transpile-modules`?
201
202- it is maintained, `@weco`'s seems dead
203- it supports TypeScript
204- it supports CSS modules (since Next.js 9.2)
205- it supports `.mjs`
206
207### A new version of Next.js is available/I just setup my project, and my build is breaking because of this plugin
208
209It is important to understand that this plugin is a big hack of the Next.js Webpack configuration. When the Next.js team pushes an update to their build configuration, the changes `next-transpile-modules` bring may be outdated, and the plugin needs to be updated (which is a breaking change for this plugin, as the updated plugin is usually not retro-compatible with the previous versions of Next.js).
210
211Now, this build problem can happen when you install your dependencies with `npm install`/`yarn install` (in your CI pipeline for example). Those commands **may re-resolve your `next` dependency of your `package.json` to a newer one**, and this newer one may have critical Webpack changes, hence breaking your build.
212
213The way to fix it is easy, and it is what you should always do: **install your dependencies with `npm ci` ("clean install") or `yarn --frozen-lockfile`**. This will force `npm` or `yarn` to use the version of Next.js declared in your lock file, instead of downloading the latest one compatible with the version accepted by your `package.json`.
214
215So basically: use your lock files right, and understand what problems they are solving ;)
216
217more:
218
219- check the [compatibility table](#compatibility-table) of this plugin
220- read more about semver and version resolutions: https://docs.npmjs.com/misc/semver
221
222### I have trouble making it work after upgrading to v5/v6
223
224Please make sure to [read the changelog](https://github.com/martpie/next-transpile-modules/releases).
225
226### I have trouble with transpilation and my custom `.babelrc`
227
228If you get a transpilation error when using a custom Babel configuration, make sure you are using a `babel.config.js` and not a `.babelrc`.
229
230The former is [a project-wide Babel configuration](https://babeljs.io/docs/en/config-files), when the latter works for relative paths only (and may not work for Yarn for example, as it installs dependencies in a parent directory).
231
232### I have trouble with Yarn and hot reloading
233
234If you add a local library (let's say with `yarn add ../some-shared-module`), Yarn will copy those files by default, instead of symlinking them. So your changes to the initial folder won't be copied to your Next.js `node_modules` directory.
235
236You can go back to `npm`, or use Yarn workspaces. See [an example](https://github.com/zeit/next.js/tree/canary/examples/with-yarn-workspaces) in the official Next.js repo.
237
238### How do I find out which package is causing a runtime exception?
239
240- add `config.optimization.minimize = false;` to you `next.config.js`'s Webpack configuration
241- run a production build
242- run it on the browser throwing the error
243- open the console, jump to the line where it failed
244- goes a little bit up in the lines of code, and check the Webpack comments telling you which module is affected
245
246### I have trouble making it work with Lerna
247
248Lerna's purpose is to publish different packages from a monorepo, **it does not help for and does not intend to help local development with local modules** (<- this, **IN CAPS**).
249
250This is not coming from me, but [from Lerna's maintainer](https://github.com/lerna/lerna/issues/1243#issuecomment-401396850).
251
252So you are probably [using it wrong](https://github.com/martpie/next-transpile-modules/issues/5#issuecomment-441501107), and I advice you to use `npm` or Yarn workspaces instead.
253
254### But... I really need to make it work with Lerna!
255
256Again, most probably a bad idea. You may need to tell your Webpack configuration how to properly resolve your scoped packages, as they won't be installed in your Next.js directory, but the root of your Lerna setup.
257
258```js
259const withTM = require('next-transpile-modules')(['@your-project/shared', '@your-project/styleguide']);
260
261module.exports = withTM({
262 webpack: (config, options) => {
263 config.resolve.alias = {
264 ...config.resolve.alias,
265 // Will make webpack look for these modules in parent directories
266 '@your-project/shared': require.resolve('@your-project/shared'),
267 '@your-project/styleguide': require.resolve('@your-project/styleguide'),
268 // ...
269 };
270 return config;
271 },
272});
273```
274
275### I have trouble with duplicated dependencies or the `Invalid hook call` error in `react`
276
277It can happen that when using `next-transpile-modules` with a local package and `npm`, you end up with duplicated dependencies in your final Next.js build. It is important to understand _why_ it happens.
278
279Let's take the following setup: one Next.js app ("Consumer"), and one Styleguide library.
280
281You will probably have `react` as a `peerDependencies` and as a `devDependecy` of the Styleguide. If you use `npm i`, it will create a symlink to your Styleguide package in your "Consumer" `node_modules`.
282
283The thing is in this shared package, you also have a `node_modules`. So when your shared modules requires, let's say `react`, Webpack will resolve it to the version in your Styleguide's `node_modules`, and not your Consumer's `node_modules`. Hence the duplicated `react` in your final bundles.
284
285You can tell Webpack how to resolve the `react` of your Styleguide to use the version in your Next.js app like that:
286
287```diff
288const withTM = require('next-transpile-modules')(['styleguide']);
289
290module.exports = withTM({
291 webpack: (config, options) => {
292+ if (options.isServer) {
293+ config.externals = ['react', ...config.externals];
294+ }
295+
296+ config.resolve.alias['react'] = path.resolve(__dirname, '.', 'node_modules', 'react');
297
298 return config
299 },
300});
301```
302
303Please note, the above [will only work](https://github.com/zeit/next.js/issues/9022#issuecomment-610255555) if `react` is properly declared as `peerDependencies` or `devDependencies` in your referenced package.
304
305It is not a great solution, but it works. Any help to find a more future-proof solution is welcome.
306
307## Credits
308
309All the honor goes to [James Gorrie](https://github.com/jamesgorrie) who created the first version of this plugin.