From eda4fce53ebbdbf86776e5cd668b1a1c257bea93 Mon Sep 17 00:00:00 2001 From: Kevin Decker Date: Sun, 9 Jul 2017 23:40:32 -0500 Subject: [PATCH] Add support for emitting node modules from webpack This has two fold impact: 1. Allows for arbitrary loaders to run in both node and browser environments (Fixes #1825) 2. Optimizes the build time for modules. Risks: - Transitions all harmony code to utilize harmony end to end. Previously this had not happened, which may break users in unexpected ways. (See #2508) - Require logic changed across the board to utilize node and webpack behaviors where possible, simplifying code, but potentially breaking things in the wild. --- package.json | 2 - server/build/babel/preset.js | 20 +---- server/build/loaders/emit-file-loader.js | 24 ------ server/build/root-module-relative-path.js | 26 ------- server/build/webpack.js | 74 ++++--------------- taskfile.js | 23 +++++- webpack/buildin/node-config.js | 8 ++ .../harmony-compatibility-template.js | 35 +++++++++ .../dependencies/harmony-export-template.js | 24 ++++++ .../harmony-import-specifier-template.js | 60 +++++++++++++++ .../dependencies/harmony-import-template.js | 52 +++++++++++++ .../dependencies/module-template-as-name.js | 19 +++++ .../dependencies/require-context-template.js | 26 +++++++ webpack/dependencies/webpack-config.js | 22 ++++++ webpack/plugins/server-source-plugin.js | 71 ++++++++++++++++++ webpack/plugins/webpack-config-plugin.js | 41 ++++++++++ webpack/utils.js | 58 +++++++++++++++ yarn.lock | 21 +----- 18 files changed, 456 insertions(+), 150 deletions(-) delete mode 100644 server/build/loaders/emit-file-loader.js delete mode 100644 server/build/root-module-relative-path.js create mode 100644 webpack/buildin/node-config.js create mode 100644 webpack/dependencies/harmony-compatibility-template.js create mode 100644 webpack/dependencies/harmony-export-template.js create mode 100644 webpack/dependencies/harmony-import-specifier-template.js create mode 100644 webpack/dependencies/harmony-import-template.js create mode 100644 webpack/dependencies/module-template-as-name.js create mode 100644 webpack/dependencies/require-context-template.js create mode 100644 webpack/dependencies/webpack-config.js create mode 100644 webpack/plugins/server-source-plugin.js create mode 100644 webpack/plugins/webpack-config-plugin.js create mode 100644 webpack/utils.js diff --git a/package.json b/package.json index 77d9b35f31adf..b24ecd34ed4f1 100644 --- a/package.json +++ b/package.json @@ -50,11 +50,9 @@ "babel-core": "6.25.0", "babel-generator": "6.25.0", "babel-loader": "7.1.1", - "babel-plugin-module-resolver": "2.6.2", "babel-plugin-react-require": "3.0.0", "babel-plugin-syntax-dynamic-import": "6.18.0", "babel-plugin-transform-class-properties": "6.24.1", - "babel-plugin-transform-es2015-modules-commonjs": "6.24.1", "babel-plugin-transform-object-rest-spread": "6.22.0", "babel-plugin-transform-react-jsx-source": "6.22.0", "babel-plugin-transform-react-remove-prop-types": "0.4.5", diff --git a/server/build/babel/preset.js b/server/build/babel/preset.js index cff127ba5bbf6..e4ffb24963f3c 100644 --- a/server/build/babel/preset.js +++ b/server/build/babel/preset.js @@ -1,5 +1,3 @@ -const relativeResolve = require('../root-module-relative-path').default(require) - const envPlugins = { 'development': [ require.resolve('babel-plugin-transform-react-jsx-source') @@ -25,22 +23,6 @@ module.exports = { require.resolve('babel-plugin-transform-class-properties'), require.resolve('babel-plugin-transform-runtime'), require.resolve('styled-jsx/babel'), - ...plugins, - [ - require.resolve('babel-plugin-module-resolver'), - { - alias: { - 'babel-runtime': relativeResolve('babel-runtime/package'), - 'next/link': relativeResolve('../../../lib/link'), - 'next/prefetch': relativeResolve('../../../lib/prefetch'), - 'next/css': relativeResolve('../../../lib/css'), - 'next/dynamic': relativeResolve('../../../lib/dynamic'), - 'next/head': relativeResolve('../../../lib/head'), - 'next/document': relativeResolve('../../../server/document'), - 'next/router': relativeResolve('../../../lib/router'), - 'next/error': relativeResolve('../../../lib/error') - } - } - ] + ...plugins ] } diff --git a/server/build/loaders/emit-file-loader.js b/server/build/loaders/emit-file-loader.js deleted file mode 100644 index 024dceb1ef891..0000000000000 --- a/server/build/loaders/emit-file-loader.js +++ /dev/null @@ -1,24 +0,0 @@ -import loaderUtils from 'loader-utils' - -module.exports = function (content, sourceMap) { - this.cacheable() - - const query = loaderUtils.getOptions(this) - const name = query.name || '[hash].[ext]' - const context = query.context || this.options.context - const regExp = query.regExp - const opts = { context, content, regExp } - const interpolatedName = loaderUtils.interpolateName(this, name, opts) - - const emit = (code, map) => { - this.emitFile(interpolatedName, code, map) - this.callback(null, code, map) - } - - if (query.transform) { - const transformed = query.transform({ content, sourceMap, interpolatedName }) - return emit(transformed.content, transformed.sourceMap) - } - - return emit(content, sourceMap) -} diff --git a/server/build/root-module-relative-path.js b/server/build/root-module-relative-path.js deleted file mode 100644 index 54557a817aca0..0000000000000 --- a/server/build/root-module-relative-path.js +++ /dev/null @@ -1,26 +0,0 @@ -// Next.js needs to use module.resolve to generate paths to modules it includes, -// but those paths need to be relative to something so that they're portable -// across directories and machines. -// -// This function returns paths relative to the top-level 'node_modules' -// directory found in the path. If none is found, returns the complete path. - -import { sep } from 'path' - -const RELATIVE_START = `node_modules${sep}` - -// Pass in the module's `require` object since it's module-specific. -export default (moduleRequire) => (path) => { - // package.json removed because babel-runtime is resolved as - // "babel-runtime/package" - const absolutePath = moduleRequire.resolve(path) - .replace(/[\\/]package\.json$/, '') - - const relativeStartIndex = absolutePath.indexOf(RELATIVE_START) - - if (relativeStartIndex === -1) { - return absolutePath - } - - return absolutePath.substring(relativeStartIndex + RELATIVE_START.length) -} diff --git a/server/build/webpack.js b/server/build/webpack.js index 9fe0064b0d877..a98329e388da8 100644 --- a/server/build/webpack.js +++ b/server/build/webpack.js @@ -7,13 +7,13 @@ import FriendlyErrorsWebpackPlugin from 'friendly-errors-webpack-plugin' import CaseSensitivePathPlugin from 'case-sensitive-paths-webpack-plugin' import UglifyJsPlugin from 'uglifyjs-webpack-plugin' import UnlinkFilePlugin from './plugins/unlink-file-plugin' +import ServerSourcePlugin from '../../webpack/plugins/server-source-plugin' +import WebpackConfigPlugin from '../../webpack/plugins/webpack-config-plugin' import PagesPlugin from './plugins/pages-plugin' import DynamicChunksPlugin from './plugins/dynamic-chunks-plugin' import CombineAssetsPlugin from './plugins/combine-assets-plugin' import getConfig from '../config' -import * as babelCore from 'babel-core' import findBabelConfig from './babel/find-config' -import rootModuleRelativePath from './root-module-relative-path' const documentPage = join('pages', '_document.js') const defaultPages = [ @@ -26,8 +26,6 @@ const interpolateNames = new Map(defaultPages.map((p) => { return [join(nextPagesDir, p), `dist/pages/${p}`] })) -const relativeResolve = rootModuleRelativePath(require) - export default async function createCompiler (dir, { dev = false, quiet = false, buildDir, conf = null } = {}) { dir = resolve(dir) const config = getConfig(dir, conf) @@ -122,6 +120,8 @@ export default async function createCompiler (dir, { dev = false, quiet = false, new webpack.DefinePlugin({ 'process.env.NODE_ENV': JSON.stringify(dev ? 'development' : 'production') }), + new ServerSourcePlugin(), + new WebpackConfigPlugin(), new PagesPlugin(), new DynamicChunksPlugin(), new CaseSensitivePathPlugin(), @@ -193,61 +193,6 @@ export default async function createCompiler (dir, { dev = false, quiet = false, .concat([{ test: /\.json$/, loader: 'json-loader' - }, { - test: /\.(js|json)(\?[^?]*)?$/, - loader: 'emit-file-loader', - include: [dir, nextPagesDir], - exclude (str) { - return /node_modules/.test(str) && str.indexOf(nextPagesDir) !== 0 - }, - options: { - name: 'dist/[path][name].[ext]', - // 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. - transform ({ content, sourceMap, interpolatedName }) { - // Only handle .js files - if (!(/\.js$/.test(interpolatedName))) { - return { content, sourceMap } - } - - const transpiled = babelCore.transform(content, { - babelrc: false, - sourceMaps: dev ? 'both' : false, - // Here we need to resolve all modules to the absolute paths. - // Earlier we did it with the babel-preset. - // But since we don't transpile ES2015 in the preset this is not resolving. - // That's why we need to do it here. - // See more: https://github.com/zeit/next.js/issues/951 - plugins: [ - [require.resolve('babel-plugin-transform-es2015-modules-commonjs')], - [ - require.resolve('babel-plugin-module-resolver'), - { - alias: { - 'babel-runtime': relativeResolve('babel-runtime/package'), - 'next/link': relativeResolve('../../lib/link'), - 'next/prefetch': relativeResolve('../../lib/prefetch'), - 'next/css': relativeResolve('../../lib/css'), - 'next/dynamic': relativeResolve('../../lib/dynamic'), - 'next/head': relativeResolve('../../lib/head'), - 'next/document': relativeResolve('../../server/document'), - 'next/router': relativeResolve('../../lib/router'), - 'next/error': relativeResolve('../../lib/error'), - 'styled-jsx/style': relativeResolve('styled-jsx/style') - } - } - ] - ], - inputSourceMap: sourceMap - }) - - return { - content: transpiled.code, - sourceMap: transpiled.map - } - } - } }, { loader: 'babel-loader', include: nextPagesDir, @@ -306,7 +251,16 @@ export default async function createCompiler (dir, { dev = false, quiet = false, }, plugins, module: { - rules + rules, + + // Avoid breaking libs such as core js Array.from and unfetch with webpack 3's + // harmony import behavior. + // + // Downstream users may want to disable this to further optimize their build + // output. + // + // See: https://github.com/webpack/webpack/issues/5111#issuecomment-309758504 + strictThisContextOnImports: true }, devtool: dev ? 'cheap-module-inline-source-map' : false, performance: { hints: false } diff --git a/taskfile.js b/taskfile.js index ac9a0a66b1bb6..ea9cb97757b10 100644 --- a/taskfile.js +++ b/taskfile.js @@ -3,7 +3,7 @@ const childProcess = require('child_process') const isWindows = /^win/.test(process.platform) export async function compile (task) { - await task.parallel(['bin', 'server', 'lib', 'client']) + await task.parallel(['bin', 'server', 'webpack', 'lib', 'client']) } export async function bin (task, opts) { @@ -21,6 +21,26 @@ export async function server (task, opts) { notify('Compiled server files') } +export async function webpack (task, opts) { + // We need to compile code that is used within webpack utilizing native classes + // to avoid issues with trying to extend webpack base classes with babel-classes + // that our lib, server, and client environments compile to (and should for + // compatibility with downstream consumers). + // + // https://github.com/babel/babel/issues/4269 + await task.source(opts.src || 'webpack/**/*.js').babel({ + presets: [ + ['env', { + targets: { + node: '5' + } + }] + ], + babelrc: false + }).target('dist/webpack') + notify('Compiled webpack files') +} + export async function client (task, opts) { await task.source(opts.src || 'client/**/*.js').babel().target('dist/client') notify('Compiled client files') @@ -39,6 +59,7 @@ export default async function (task) { await task.watch('bin/*', 'bin') await task.watch('pages/**/*.js', 'copy') await task.watch('server/**/*.js', 'server') + await task.watch('webpack/**/*.js', 'webpack') await task.watch('client/**/*.js', 'client') await task.watch('lib/**/*.js', 'lib') } diff --git a/webpack/buildin/node-config.js b/webpack/buildin/node-config.js new file mode 100644 index 0000000000000..4740203be554c --- /dev/null +++ b/webpack/buildin/node-config.js @@ -0,0 +1,8 @@ +// Webpack ApiPlugin aliases for node execution environment. +// https://github.com/webpack/webpack/blob/master/lib/APIPlugin.js +// +// Only used within the next server process and will be properly populated +// via the next runtime. + +// __webpack_require__.p = the bundle public path +exports.publicPath = '/_next/webpack' diff --git a/webpack/dependencies/harmony-compatibility-template.js b/webpack/dependencies/harmony-compatibility-template.js new file mode 100644 index 0000000000000..22b9b6cf500f5 --- /dev/null +++ b/webpack/dependencies/harmony-compatibility-template.js @@ -0,0 +1,35 @@ +export default class HarmonyCompatibilityDependencyTemplate { + apply (dep, source) { + source.insert(-10, ` + const __webpack_exports__ = module.exports = exports = {}; + Object.defineProperty(__webpack_exports__, "__esModule", { value: true }); + + const __webpack_require__ = require; + + __webpack_require__.o = function(object, property) { + return Object.prototype.hasOwnProperty.call(object, property); + }; + + // Via MainTemplate + // define getter function for harmony exports + __webpack_require__.d = function(exports, name, getter) { + if(!__webpack_require__.o(exports, name)) { + Object.defineProperty(exports, name, { + configurable: false, + enumerable: true, + get: getter + }); + } + }; + + // getDefaultExport function for compatibility with non-harmony modules"); + __webpack_require__.n = function(module) { + var getter = module && module.__esModule ? + function getDefault() { return module['default']; } : + function getModuleExports() { return module; }; + __webpack_require__.d(getter, 'a', getter); + return getter; + }; +`) + } +}; diff --git a/webpack/dependencies/harmony-export-template.js b/webpack/dependencies/harmony-export-template.js new file mode 100644 index 0000000000000..35f86a8e5fd03 --- /dev/null +++ b/webpack/dependencies/harmony-export-template.js @@ -0,0 +1,24 @@ +export default class HarmonyExportDependencyTemplate { + apply (dep, source) { + const used = dep.originModule.isUsed('default') + const content = this.getContent(dep.originModule, used) + + if (dep.range) { + source.replace(dep.rangeStatement[0], dep.range[0] - 1, `${content}(`) + source.replace(dep.range[1], dep.rangeStatement[1] - 1, ');') + return + } + + source.replace(dep.rangeStatement[0], dep.rangeStatement[1] - 1, content) + } + + getContent (module, used) { + const exportsName = module.exportsArgument || 'exports' + if (used) { + return `/* harmony default export */ ${exportsName}[${JSON.stringify( + used + )}] = ` + } + return '/* unused harmony default export */ var _unused_webpack_default_export = ' + } +} diff --git a/webpack/dependencies/harmony-import-specifier-template.js b/webpack/dependencies/harmony-import-specifier-template.js new file mode 100644 index 0000000000000..05d302e1d0cb5 --- /dev/null +++ b/webpack/dependencies/harmony-import-specifier-template.js @@ -0,0 +1,60 @@ +export default class HarmonyImportSpecifierDependencyTemplate { + apply (dep, source) { + const content = this.getContent(dep) + source.replace(dep.range[0], dep.range[1] - 1, content) + } + + getContent (dep) { + const importedModule = dep.importDependency.module + const defaultImport = + dep.directImport && + dep.id === 'default' && + !( + importedModule && + (!importedModule.meta || importedModule.meta.harmonyModule) && + !/node_modules/.test(importedModule.resource) + ) + const shortHandPrefix = this.getShortHandPrefix(dep) + const { importedVar } = dep + const importedVarSuffix = this.getImportVarSuffix( + dep, + defaultImport, + importedModule + ) + + if (dep.call && defaultImport) { + return `${shortHandPrefix}${importedVar}_default()` + } + + if (dep.call && dep.id) { + return `${shortHandPrefix}Object(${importedVar}${importedVarSuffix})` + } + + return `${shortHandPrefix}${importedVar}${importedVarSuffix}` + } + + getImportVarSuffix (dep, defaultImport, importedModule) { + if (defaultImport) { + return '_default.a' + } + + if (dep.id) { + const used = + importedModule && !/node_modules/.test(importedModule.resource) + ? importedModule.isUsed(dep.id) + : dep.id + const optionalComment = dep.id !== used ? ` /* ${dep.id} */` : '' + return `[${JSON.stringify(used)}${optionalComment}]` + } + + return '' + } + + getShortHandPrefix (dep) { + if (!dep.shorthand) { + return '' + } + + return `${dep.name}: ` + } +} diff --git a/webpack/dependencies/harmony-import-template.js b/webpack/dependencies/harmony-import-template.js new file mode 100644 index 0000000000000..a352f5728c598 --- /dev/null +++ b/webpack/dependencies/harmony-import-template.js @@ -0,0 +1,52 @@ +import { requireValue } from '../utils' + +export default class HarmonyImportDependencyTemplate { + apply (dep, source, outputOptions, requestShortener) { + const content = makeImportStatement( + true, + dep, + outputOptions, + requestShortener + ) + source.replace(dep.range[0], dep.range[1] - 1, '') + source.insert(-1, content) + } +}; + +function makeImportStatement (declare, dep, outputOptions, requestShortener) { + const newline = declare ? '\n' : ' ' + + if (!dep.module) { + const stringifiedError = JSON.stringify( + `Cannot find module "${dep.request}"` + ) + return `throw new Error(${stringifiedError});${newline}` + } + + if (dep.importedVar) { + const declaration = `${declare ? 'var ' : ''}${dep.importedVar}` + const request = requireValue(dep, requestShortener) + const isHarmonyModule = + dep.module.meta && + dep.module.meta.harmonyModule && + !/node_modules/.test(dep.module.resource) + + const content = `/* harmony import */ ${ + declaration + } = __webpack_require__(${ + request + });\n` + + if (isHarmonyModule) { + return content + } + + return `${content}/* harmony import */ ${ + declaration + }_default = __webpack_require__.n(${ + dep.importedVar + });${newline}` + } + + return '' +} diff --git a/webpack/dependencies/module-template-as-name.js b/webpack/dependencies/module-template-as-name.js new file mode 100644 index 0000000000000..d5611add5a801 --- /dev/null +++ b/webpack/dependencies/module-template-as-name.js @@ -0,0 +1,19 @@ +import WebpackMissingModule from 'webpack/lib/dependencies/WebpackMissingModule' +import { requireValue } from '../utils' + +export default class RequireContextDependencyTemplate { + apply (dep, source, outputOptions, requestShortener) { + if (!dep.range) { + return + } + + const request = requireValue(dep, requestShortener) + let content + if (dep.module) { + content = request + } else { + content = WebpackMissingModule.module(request) + } + source.replace(dep.range[0], dep.range[1] - 1, content) + } +} diff --git a/webpack/dependencies/require-context-template.js b/webpack/dependencies/require-context-template.js new file mode 100644 index 0000000000000..6d8288b8d20ff --- /dev/null +++ b/webpack/dependencies/require-context-template.js @@ -0,0 +1,26 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +'use strict' + +import WebpackMissingModule from 'webpack/lib/dependencies/WebpackMissingModule' +import { requireValue } from '../utils' + +export default class RequireContextDependencyTemplate { + apply (dep, source, outputOptions, requestShortener) { + if (!dep.range) { + return + } + + const request = requireValue(dep, requestShortener) + let content + if (dep.module) { + content = `__webpack_require__(${request})` + } else { + content = WebpackMissingModule.module(request) + } + source.replace(dep.range[0], dep.range[1] - 1, content) + } +} diff --git a/webpack/dependencies/webpack-config.js b/webpack/dependencies/webpack-config.js new file mode 100644 index 0000000000000..e833bd4153fc9 --- /dev/null +++ b/webpack/dependencies/webpack-config.js @@ -0,0 +1,22 @@ +'use strict' + +import ConstDependency from 'webpack/lib/dependencies/ConstDependency' + +export default class WebpackConfigDependency extends ConstDependency { + constructor (expression, nodeExpression, range) { + super(expression, range) + this.nodeExpression = nodeExpression + } + + get type () { + return 'webpack config' + } +} + +WebpackConfigDependency.Template = ConstDependency.Template +WebpackConfigDependency.NodeTemplate = class WebpackConfigDependencyTemplate { + apply (dep, source) { + const content = `require("next/webpack/buildin/node-config").${dep.nodeExpression}` + source.replace(dep.range[0], dep.range[1] - 1, content) + } +} diff --git a/webpack/plugins/server-source-plugin.js b/webpack/plugins/server-source-plugin.js new file mode 100644 index 0000000000000..91679e1f906b9 --- /dev/null +++ b/webpack/plugins/server-source-plugin.js @@ -0,0 +1,71 @@ +'use strict' + +import HarmonyCompatibilityDependency from 'webpack/lib/dependencies/HarmonyCompatibilityDependency' +import HarmonyCompatibilityDependencyTemplate from '../dependencies/harmony-compatibility-template' + +import WebpackConfigDependency from '../dependencies/webpack-config' + +import HarmonyImportDependency from 'webpack/lib/dependencies/HarmonyImportDependency' +import HarmonyImportDependencyTemplate from '../dependencies/harmony-import-template' +import HarmonyImportSpecifierDependency from 'webpack/lib/dependencies/HarmonyImportSpecifierDependency' +import HarmonyImportSpecifierDependencyTemplate from '../dependencies/harmony-import-specifier-template' +import RequireContextDependency from 'webpack/lib/dependencies/RequireContextDependency' +import RequireContextDependencyTemplate from '../dependencies/require-context-template' +import CommonJsRequireDependency from 'webpack/lib/dependencies/CommonJsRequireDependency' +import ModuleDependencyTemplateAsName from '../dependencies/module-template-as-name' +import { nodeModuleName } from '../utils' + +export default class ServerSourcePlugin { + apply (compiler) { + compiler.plugin('compilation', (compilation) => { + compilation.plugin('before-module-assets', () => { + // Swap out the templates for pertinent dependencies to render + // node file system specific imports and exports + const serverDependencyTemplates = new Map( + compilation.dependencyTemplates + ) + serverDependencyTemplates.set( + WebpackConfigDependency, + new WebpackConfigDependency.NodeTemplate() + ) + serverDependencyTemplates.set( + HarmonyCompatibilityDependency, + new HarmonyCompatibilityDependencyTemplate() + ) + serverDependencyTemplates.set( + HarmonyImportDependency, + new HarmonyImportDependencyTemplate() + ) + serverDependencyTemplates.set( + HarmonyImportSpecifierDependency, + new HarmonyImportSpecifierDependencyTemplate() + ) + serverDependencyTemplates.set( + RequireContextDependency, + new RequireContextDependencyTemplate() + ) + serverDependencyTemplates.set( + CommonJsRequireDependency, + new ModuleDependencyTemplateAsName() + ) + + compilation.modules + .filter( + ({ rootModule, resource }) => + (rootModule || resource) && !/node_modules|next\.js\/dist\//.test(resource) + ) + .forEach((module) => { + const assetName = `dist/${nodeModuleName(module)}` + + module.assets = module.assets || {} + + module.assets[assetName] = module.source( + serverDependencyTemplates, + compilation.outputOptions, + compilation.moduleTemplate.requestShortener + ) + }) + }) + }) + } +}; diff --git a/webpack/plugins/webpack-config-plugin.js b/webpack/plugins/webpack-config-plugin.js new file mode 100644 index 0000000000000..a7d103998974f --- /dev/null +++ b/webpack/plugins/webpack-config-plugin.js @@ -0,0 +1,41 @@ +'use strict' + +import ParserHelpers from 'webpack/lib/ParserHelpers' +import NullFactory from 'webpack/lib/NullFactory' +import WebpackConfigDependency from '../dependencies/webpack-config' + +export default class WebpackConfigPlugin { + apply (compiler) { + compiler.plugin('compilation', (compilation, params) => { + compilation.dependencyFactories.set( + WebpackConfigDependency, + new NullFactory() + ) + compilation.dependencyTemplates.set( + WebpackConfigDependency, + new WebpackConfigDependency.Template() + ) + + params.normalModuleFactory.plugin('parser', (parser) => { + parser.plugin( + 'expression __webpack_public_path__', + function publicPathExpression (expr) { + const dep = new WebpackConfigDependency( + '__webpack_require__.p', + 'publicPath', + expr.range + ) + dep.loc = expr.loc + this.state.current.addDependency(dep) + return true + } + ) + + parser.plugin( + 'evaluate typeof __webpack_public_path__', + ParserHelpers.evaluateToString('string') + ) + }) + }) + } +} diff --git a/webpack/utils.js b/webpack/utils.js new file mode 100644 index 0000000000000..dc8e9ad316321 --- /dev/null +++ b/webpack/utils.js @@ -0,0 +1,58 @@ +import Crypto from 'crypto' +import { relative, dirname } from 'path' + +export function requireValue (dep, requestShortener) { + let { module } = dep + + if (!module) { + return null + } + + if (!module.resource) { + // Handle ConcatenatedModule + module = module.rootModule + } + + // Handle references to content stored in node_modules + if (module.resource.includes('node_modules') || module.resource.includes('next.js/dist/')) { + return JSON.stringify(requestShortener.shorten(dep.request).replace(/.*?node_modules\//, '')) + } + + const fingerprint = loaderFingerprint(module) + const reason = (module.reasons.find(({ dependency }) => dependency === dep) || {module: module.issuer}).module + let request = relative(dirname(reason.resource || reason.rootModule.resource), module.resource) + if (!/^\./.test(request)) { + request = `./${request}` + } + + return JSON.stringify(`${request}_${fingerprint}`) +}; + +export function nodeModuleName (module) { + let { resource, rootModule } = module + + if (!resource && rootModule) { + resource = rootModule.resource + } + + /* istanbul ignore next : Sanity */ + if (!resource) { + throw new Error('Only file backed loaders are supported') + } + + const relativeName = relative(process.cwd(), resource) + + return `${relativeName}_${exports.loaderFingerprint(module)}` +}; + +export function loaderFingerprint (module) { + const hash = Crypto.createHash('md5') + + const { loaders = [] } = module + loaders.forEach(({ loader, ident = '' }) => { + hash.update(loader) + hash.update(ident) + }) + + return `${hash.digest('hex')}.js` +}; diff --git a/yarn.lock b/yarn.lock index d28d6a9df9e63..7d2a776c24d33 100644 --- a/yarn.lock +++ b/yarn.lock @@ -515,14 +515,6 @@ babel-plugin-jest-hoist@^20.0.3: version "20.0.3" resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-20.0.3.tgz#afedc853bd3f8dc3548ea671fbe69d03cc2c1767" -babel-plugin-module-resolver@2.6.2: - version "2.6.2" - resolved "https://registry.yarnpkg.com/babel-plugin-module-resolver/-/babel-plugin-module-resolver-2.6.2.tgz#66845c8855865dd7fd4d5256be93272e3d16701d" - dependencies: - find-babel-config "^1.0.1" - glob "^7.1.1" - resolve "^1.3.2" - babel-plugin-react-require@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/babel-plugin-react-require/-/babel-plugin-react-require-3.0.0.tgz#2e4e7b4496b93a654a1c80042276de4e4eeb20e3" @@ -660,7 +652,7 @@ babel-plugin-transform-es2015-modules-amd@^6.22.0, babel-plugin-transform-es2015 babel-runtime "^6.22.0" babel-template "^6.24.1" -babel-plugin-transform-es2015-modules-commonjs@6.24.1, babel-plugin-transform-es2015-modules-commonjs@^6.23.0, babel-plugin-transform-es2015-modules-commonjs@^6.24.1: +babel-plugin-transform-es2015-modules-commonjs@^6.23.0, babel-plugin-transform-es2015-modules-commonjs@^6.24.1: version "6.24.1" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.24.1.tgz#d3e310b40ef664a36622200097c6d440298f2bfe" dependencies: @@ -2281,13 +2273,6 @@ finalhandler@~1.0.0: statuses "~1.3.1" unpipe "~1.0.0" -find-babel-config@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/find-babel-config/-/find-babel-config-1.1.0.tgz#acc01043a6749fec34429be6b64f542ebb5d6355" - dependencies: - json5 "^0.5.1" - path-exists "^3.0.0" - find-cache-dir@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-0.1.1.tgz#c8defae57c8a52a8a784f9e31c57c742e993a0b9" @@ -2484,7 +2469,7 @@ glob-promise@3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/glob-promise/-/glob-promise-3.1.0.tgz#198882a3817be7dc2c55f92623aa9e7b3f82d1eb" -glob@7.1.1, glob@^7.0.6, glob@^7.1.1: +glob@7.1.1, glob@^7.0.6: version "7.1.1" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.1.tgz#805211df04faaf1c63a3600306cdf5ade50b2ec8" dependencies: @@ -2505,7 +2490,7 @@ glob@^6.0.1: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.2: +glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.1, glob@^7.1.2: version "7.1.2" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" dependencies: