From a5e56f2272daa9d3788ed2eec490a210e684ab2e Mon Sep 17 00:00:00 2001 From: Giuseppe Gurgone Date: Wed, 22 Nov 2017 21:31:12 +0100 Subject: [PATCH 1/3] Add support for transpile packages installed via npm --- server/build/webpack.js | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/server/build/webpack.js b/server/build/webpack.js index ba0ec11b351a2..f2b3821d74f66 100644 --- a/server/build/webpack.js +++ b/server/build/webpack.js @@ -186,6 +186,18 @@ export default async function createCompiler (dir, { buildId, dev = false, quiet mainBabelOptions.presets.push(require.resolve('./babel/preset')) } + function shouldTranspileModule (path) { + return (config.transpileModules || []).some(pattern => { + if (!(pattern instanceof RegExp)) { + const message = `Incorrect pattern in config.transpileModules: "${pattern}".` + + 'It accepts an array of regular expression' + throw new Error(message) + } + + return pattern.test(path) + }) + } + const rules = (dev ? [{ test: /\.js(\?[^?]*)?$/, loader: 'hot-self-accept-loader', @@ -206,6 +218,10 @@ export default async function createCompiler (dir, { buildId, dev = false, quiet loader: 'emit-file-loader', include: [dir, nextPagesDir], exclude (str) { + if (shouldTranspileModule(str)) { + return false + } + return /node_modules/.test(str) && str.indexOf(nextPagesDir) !== 0 }, options: { @@ -290,6 +306,9 @@ export default async function createCompiler (dir, { buildId, dev = false, quiet loader: 'babel-loader', include: [dir], exclude (str) { + if (shouldTranspileModule(str)) { + return false + } return /node_modules/.test(str) }, options: mainBabelOptions From 45ba2299524c322a3db5e85b25726afaa3f29dbf Mon Sep 17 00:00:00 2001 From: Giuseppe Gurgone Date: Sun, 26 Nov 2017 14:15:47 +0100 Subject: [PATCH 2/3] Add example and docs --- examples/transpile-node-modules/app/.babelrc | 5 ++++ .../transpile-node-modules/app/next.config.js | 5 ++++ .../transpile-node-modules/app/package.json | 22 ++++++++++++++ .../transpile-node-modules/app/pages/index.js | 24 +++++++++++++++ .../component-a/constants.js | 1 + .../component-a/lib/zeit.js | 30 +++++++++++++++++++ .../component-a/package.json | 15 ++++++++++ .../component-b/index.js | 16 ++++++++++ .../component-b/package.json | 13 ++++++++ readme.md | 22 ++++++++++++++ server/build/webpack.js | 1 + 11 files changed, 154 insertions(+) create mode 100644 examples/transpile-node-modules/app/.babelrc create mode 100644 examples/transpile-node-modules/app/next.config.js create mode 100644 examples/transpile-node-modules/app/package.json create mode 100644 examples/transpile-node-modules/app/pages/index.js create mode 100644 examples/transpile-node-modules/component-a/constants.js create mode 100644 examples/transpile-node-modules/component-a/lib/zeit.js create mode 100644 examples/transpile-node-modules/component-a/package.json create mode 100644 examples/transpile-node-modules/component-b/index.js create mode 100644 examples/transpile-node-modules/component-b/package.json diff --git a/examples/transpile-node-modules/app/.babelrc b/examples/transpile-node-modules/app/.babelrc new file mode 100644 index 0000000000000..d44724705b94a --- /dev/null +++ b/examples/transpile-node-modules/app/.babelrc @@ -0,0 +1,5 @@ +{ + "presets": [ + "next/babel" + ] +} diff --git a/examples/transpile-node-modules/app/next.config.js b/examples/transpile-node-modules/app/next.config.js new file mode 100644 index 0000000000000..b7c454a83a16d --- /dev/null +++ b/examples/transpile-node-modules/app/next.config.js @@ -0,0 +1,5 @@ +module.exports = { + transpileModules: [ + /node_modules\/component-[a|b]/ + ] +} diff --git a/examples/transpile-node-modules/app/package.json b/examples/transpile-node-modules/app/package.json new file mode 100644 index 0000000000000..cbca725c4a23b --- /dev/null +++ b/examples/transpile-node-modules/app/package.json @@ -0,0 +1,22 @@ +{ + "private": true, + "name": "app", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "dev": "next", + "build": "next build", + "start": "next start" + }, + "keywords": [], + "author": "Giuseppe Gurgone", + "license": "MIT", + "dependencies": { + "component-a": "file:../component-a", + "component-b": "file:../component-b", + "next": "file:../../../", + "react": "^16.1.1", + "react-dom": "^16.1.1" + } +} diff --git a/examples/transpile-node-modules/app/pages/index.js b/examples/transpile-node-modules/app/pages/index.js new file mode 100644 index 0000000000000..07c0532fc4f8b --- /dev/null +++ b/examples/transpile-node-modules/app/pages/index.js @@ -0,0 +1,24 @@ +import Logo from 'component-a' + +export default () => ( +
+ + +
+) diff --git a/examples/transpile-node-modules/component-a/constants.js b/examples/transpile-node-modules/component-a/constants.js new file mode 100644 index 0000000000000..dac39c0894ae9 --- /dev/null +++ b/examples/transpile-node-modules/component-a/constants.js @@ -0,0 +1 @@ +export const color = 'black' diff --git a/examples/transpile-node-modules/component-a/lib/zeit.js b/examples/transpile-node-modules/component-a/lib/zeit.js new file mode 100644 index 0000000000000..17230f7960104 --- /dev/null +++ b/examples/transpile-node-modules/component-a/lib/zeit.js @@ -0,0 +1,30 @@ +import {color} from '../constants' +import Logo from 'component-b' + +export default () => ( +

+ Zeit + + +

+) diff --git a/examples/transpile-node-modules/component-a/package.json b/examples/transpile-node-modules/component-a/package.json new file mode 100644 index 0000000000000..2eb2f21e3cc98 --- /dev/null +++ b/examples/transpile-node-modules/component-a/package.json @@ -0,0 +1,15 @@ +{ + "name": "component-a", + "version": "1.0.0", + "description": "a test component", + "main": "lib/zeit.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "Giuseppe Gurgone", + "license": "MIT", + "dependencies": { + "component-b": "file:../component-b" + } +} diff --git a/examples/transpile-node-modules/component-b/index.js b/examples/transpile-node-modules/component-b/index.js new file mode 100644 index 0000000000000..61863b069504c --- /dev/null +++ b/examples/transpile-node-modules/component-b/index.js @@ -0,0 +1,16 @@ +export default () => ( + + + +) diff --git a/examples/transpile-node-modules/component-b/package.json b/examples/transpile-node-modules/component-b/package.json new file mode 100644 index 0000000000000..834e9330a7171 --- /dev/null +++ b/examples/transpile-node-modules/component-b/package.json @@ -0,0 +1,13 @@ +{ + "private": true, + "name": "component-b", + "version": "1.0.0", + "description": "b test component", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "Giuseppe Gurgone", + "license": "MIT" +} diff --git a/readme.md b/readme.md index 25d41160eee15..959a27f54d46c 100644 --- a/readme.md +++ b/readme.md @@ -1026,6 +1026,28 @@ module.exports = { Note: Next.js will automatically use that prefix the scripts it loads, but this has no effect whatsoever on `/static`. If you want to serve those assets over the CDN, you'll have to introduce the prefix yourself. One way of introducing a prefix that works inside your components and varies by environment is documented [in this example](https://github.com/zeit/next.js/tree/master/examples/with-universal-configuration). +### Transpile NPM modules + +

+ Examples + +

+ +By default Next.js won't transpile your app dependencies (`node_modules`) such as third parties libraries or components. However it still makes it possible to selectively transform some of them (or all if you will) via the `transpileModules` option in `next.config.js`. + +```js +module.exports = { + transpileModules: [ + /node_modules\/react-button/ + ] +} +``` + +`transpileModules` must be an array of regular expressions. When building, Next.js will transpile all of the modules that match at least one of the regular expressions, using the `.babelrc` configuration and falling back to `next/babel` if you don't have one. + +> Note: Currently, Next.js only supports modules installed into `node_modules`. Transpiling modules linked with `npm link ` might not work as expected or at all. + + ## Production deployment To deploy, instead of running `next`, you want to build for production usage ahead of time. Therefore, building and starting are separate commands: diff --git a/server/build/webpack.js b/server/build/webpack.js index f2b3821d74f66..e47f418352592 100644 --- a/server/build/webpack.js +++ b/server/build/webpack.js @@ -309,6 +309,7 @@ export default async function createCompiler (dir, { buildId, dev = false, quiet if (shouldTranspileModule(str)) { return false } + return /node_modules/.test(str) }, options: mainBabelOptions From a5775291845e496b26533d915d2a3a67019a0704 Mon Sep 17 00:00:00 2001 From: Giuseppe Gurgone Date: Sun, 24 Dec 2017 12:25:23 +0100 Subject: [PATCH 3/3] Copy package.json POF --- server/build/loaders/emit-file-loader.js | 7 +++++ server/build/webpack.js | 40 ++++++++++++++++++++++-- 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/server/build/loaders/emit-file-loader.js b/server/build/loaders/emit-file-loader.js index 372112f98cc5c..4c529672a9f3b 100644 --- a/server/build/loaders/emit-file-loader.js +++ b/server/build/loaders/emit-file-loader.js @@ -28,6 +28,13 @@ module.exports = function (content, sourceMap) { callback(null, code, map) } + if (query.copyPackageJson) { + const info = query.copyPackageJson(interpolatedName) + if (info) { + this.emitFile(info.interpolatedName, info.content) + } + } + if (query.transform) { const transformed = query.transform({ content, sourceMap, interpolatedName }) return emit(transformed.content, transformed.sourceMap) diff --git a/server/build/webpack.js b/server/build/webpack.js index 6de27ba5fe774..97caf39e7b92d 100644 --- a/server/build/webpack.js +++ b/server/build/webpack.js @@ -1,6 +1,6 @@ -import { resolve, join, sep } from 'path' +import { resolve, join, sep, dirname } from 'path' import { createHash } from 'crypto' -import { realpathSync, existsSync } from 'fs' +import { realpathSync, existsSync, readFileSync } from 'fs' import webpack from 'webpack' import glob from 'glob-promise' import WriteFilePlugin from 'write-file-webpack-plugin' @@ -203,6 +203,24 @@ export default async function createCompiler (dir, { buildId, dev = false, quiet }) } + function getPackageJsonPath (filePath) { + let filePathDirname = dirname(filePath) + if (filePathDirname === dir) { + return join(dir, 'package.json') + } + + while (dir !== filePathDirname) { + const packageJsonPath = join(filePathDirname, 'package.json') + if (existsSync(packageJsonPath)) { + return packageJsonPath + } + filePathDirname = dirname(filePathDirname) + } + } + + const packageJsonToCopy = {} + const copiedPackageJson = {} + const rules = (dev ? [{ test: /\.(js|jsx)(\?[^?]*)?$/, loader: 'hot-self-accept-loader', @@ -224,6 +242,12 @@ export default async function createCompiler (dir, { buildId, dev = false, quiet include: [dir, nextPagesDir], exclude (str) { if (shouldTranspileModule(str)) { + if (!packageJsonToCopy[str]) { + const packageJsonPath = getPackageJsonPath(str) + if (packageJsonPath) { + packageJsonToCopy[str.replace(dir, '')] = packageJsonPath + } + } return false } @@ -243,12 +267,22 @@ export default async function createCompiler (dir, { buildId, dev = false, quiet } const filePath = file.slice(0, -(from.length)) + to - if (existsSync(filePath)) { throw new Error(`Both ${from} and ${to} file found. Please make sure you only have one of both.`) } } }, + copyPackageJson (interpolatedName) { + const packageJsonPath = packageJsonToCopy[interpolatedName.replace(/^dist/, '')] + + if (packageJsonPath && !copiedPackageJson[packageJsonPath]) { + copiedPackageJson[packageJsonPath] = true + return { + interpolatedName: join('dist', packageJsonPath.replace(dir, '')), + content: readFileSync(packageJsonPath, 'utf-8') + } + } + }, // By default, our babel config does not transpile ES2015 module syntax because // webpack knows how to handle them. (That's how it can do tree-shaking) // But Node.js doesn't know how to handle them. So, we have to transpile them here.