diff --git a/packages/razzle-dev-utils/package.json b/packages/razzle-dev-utils/package.json index adb029993..0525d74b0 100644 --- a/packages/razzle-dev-utils/package.json +++ b/packages/razzle-dev-utils/package.json @@ -20,6 +20,7 @@ "chalk": "3.0.0", "jest-message-util": "^24.9.0", "react-dev-utils": "^10.2.0", + "resolve": "1.17.0", "strip-ansi": "6.0.0" } } diff --git a/packages/razzle-dev-utils/resolveRequest.js b/packages/razzle-dev-utils/resolveRequest.js new file mode 100644 index 000000000..f780cc1cb --- /dev/null +++ b/packages/razzle-dev-utils/resolveRequest.js @@ -0,0 +1,12 @@ +const resolve = require('resolve'); +const path = require('path'); + +function resolveRequest(req, issuer) { + const basedir = + issuer.endsWith(path.posix.sep) || issuer.endsWith(path.win32.sep) + ? issuer + : path.dirname(issuer); + return resolve.sync(req, { basedir }); +}; + +module.exports = resolveRequest; diff --git a/packages/razzle/config/createConfigAsync.js b/packages/razzle/config/createConfigAsync.js index 6aaa2a844..8615208e3 100644 --- a/packages/razzle/config/createConfigAsync.js +++ b/packages/razzle/config/createConfigAsync.js @@ -19,6 +19,7 @@ const WebpackBar = require('webpackbar'); const ManifestPlugin = require('webpack-manifest-plugin'); const modules = require('./modules'); const postcssLoadConfig = require('postcss-load-config'); +const resolveRequest = require('razzle-dev-utils/resolveRequest'); const logger = require('razzle-dev-utils/logger'); const razzlePaths = require('razzle/config/paths'); const getCacheIdentifier = require('react-dev-utils/getCacheIdentifier'); @@ -138,6 +139,89 @@ module.exports = ( const additionalAliases = modulesConfig.additionalAliases || {}; const additionalIncludes = modulesConfig.additionalIncludes || []; + const nodeExternalsFunc = experimental.newExternals ? + (context, request, callback) => { + + if ((experimental.newExternals.notExternalModules || []).indexOf(request) !== -1) { + return callback() + } + + const isLocal = + request.startsWith('.') || + // Always check for unix-style path, as webpack sometimes + // normalizes as posix. + path.posix.isAbsolute(request) || + // When on Windows, we also want to check for Windows-specific + // absolute paths. + (process.platform === 'win32' && path.win32.isAbsolute(request)) + + // Relative requires don't need custom resolution, because they + // are relative to requests we've already resolved here. + // Absolute requires (require('/foo')) are extremely uncommon, but + // also have no need for customization as they're already resolved. + if (isLocal) { + return callback() + } + + let res; + try { + res = resolveRequest(request, `${context}/`) + } catch (err) { + // If the request cannot be resolved, we need to tell webpack to + // "bundle" it so that webpack shows an error (that it cannot be + // resolved). + return callback(); + } + // Same as above, if the request cannot be resolved we need to have + // webpack "bundle" it so it surfaces the not found error. + if (!res) { + return callback() + } + // This means we need to make sure its request resolves to the same + // package that'll be available at runtime. If it's not identical, + // we need to bundle the code (even if it _should_ be external). + let baseRes = null; + try { + baseRes = resolveRequest(request, `${paths.appPath}/`) + } catch (err) { + baseRes = null + } + + // Same as above: if the package, when required from the root, + // would be different from what the real resolution would use, we + // cannot externalize it. + if (baseRes !== res) { + return callback() + } + // Anything else that is standard JavaScript within `node_modules` + // can be externalized. + if (res.match(/node_modules[/\\].*\.js$/)) { + const externalRequest = + path.posix.join( + paths.appPath, + path + .relative( + paths.appPath, + res + ) + // Windows path normalization + .replace(/\\/g, '/') + ) + return callback(undefined, `commonjs ${externalRequest}`) + } + + // Default behavior: bundle the code! + return callback() + } : nodeExternals({ + whitelist: [ + IS_DEV ? 'webpack/hot/poll?300' : null, + /\.(eot|woff|woff2|ttf|otf)$/, + /\.(svg|png|jpg|jpeg|gif|ico)$/, + /\.(mp4|mp3|ogg|swf|webp)$/, + /\.(css|scss|sass|sss|less)$/, + ].filter(x => x), + }); + // This is our base webpack config. let config = { // Set webpack mode: @@ -296,17 +380,7 @@ module.exports = ( }; // We need to tell webpack what to bundle into our Node bundle. - config.externals = [ - nodeExternals({ - whitelist: [ - IS_DEV ? 'webpack/hot/poll?300' : null, - /\.(eot|woff|woff2|ttf|otf)$/, - /\.(svg|png|jpg|jpeg|gif|ico)$/, - /\.(mp4|mp3|ogg|swf|webp)$/, - /\.(css|scss|sass|sss|less)$/, - ].filter(x => x), - }), - ]; + config.externals = [nodeExternalsFunc]; // Specify webpack Node.js output path and filename config.output = { diff --git a/yarn.lock b/yarn.lock index f9f6d27a3..94d129bd9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14926,7 +14926,7 @@ resolve@1.1.7: resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs= -resolve@^1.1.6, resolve@^1.10.0, resolve@^1.12.0, resolve@^1.13.1, resolve@^1.15.1, resolve@^1.17.0, resolve@^1.3.2, resolve@^1.8.1: +resolve@1.17.0, resolve@^1.1.6, resolve@^1.10.0, resolve@^1.12.0, resolve@^1.13.1, resolve@^1.15.1, resolve@^1.17.0, resolve@^1.3.2, resolve@^1.8.1: version "1.17.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444" integrity sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==