rsnext/packages/next/build/webpack/loaders/next-babel-loader.js
Guy Bedford 64850a8348
ncc Babel inlining (#18768)
This adds inlining for Babel and the Babel plugins used in next.

This is based to the PR at https://github.com/vercel/next.js/pull/18823.

The approach is to make one large bundle and then separate out the individual packages from that in order to avoid duplications.

In the first attempt the Babel bundle size was 10MB... using "resolutions" in the Yarn workspace to reduce the duplicated packages this was brought down to a 2.8MB bundle for Babel and all the used plugins which is exactly the expected file size here.

This will thus add a 2.8MB download size to the next package, but save downloading any babel dependencies separately, removing a large number of package dependencies from the overall install.
2020-11-05 14:23:01 +00:00

268 lines
8 KiB
JavaScript

import babelLoader from 'next/dist/compiled/babel-loader'
import hash from 'next/dist/compiled/string-hash'
import { basename, join } from 'path'
import * as Log from '../../output/log'
// increment 'n' to invalidate cache
// eslint-disable-next-line no-useless-concat
const cacheKey = 'babel-cache-' + 'n' + '-'
const nextBabelPreset = require('../../babel/preset')
const getModernOptions = (babelOptions = {}) => {
const presetEnvOptions = Object.assign({}, babelOptions['preset-env'])
const transformRuntimeOptions = Object.assign(
{},
babelOptions['transform-runtime'],
{ regenerator: false }
)
presetEnvOptions.targets = {
esmodules: true,
}
presetEnvOptions.exclude = [
...(presetEnvOptions.exclude || []),
// Block accidental inclusions
'transform-regenerator',
'transform-async-to-generator',
]
return {
...babelOptions,
'preset-env': presetEnvOptions,
'transform-runtime': transformRuntimeOptions,
}
}
const nextBabelPresetModern = (presetOptions) => (context) =>
nextBabelPreset(context, getModernOptions(presetOptions))
module.exports = babelLoader.custom((babel) => {
const presetItem = babel.createConfigItem(nextBabelPreset, {
type: 'preset',
})
const applyCommonJs = babel.createConfigItem(
require('../../babel/plugins/commonjs'),
{ type: 'plugin' }
)
const commonJsItem = babel.createConfigItem(
require('next/dist/compiled/babel/plugin-transform-modules-commonjs'),
{ type: 'plugin' }
)
const configs = new Set()
return {
customOptions(opts) {
const custom = {
isServer: opts.isServer,
isModern: opts.isModern,
pagesDir: opts.pagesDir,
hasModern: opts.hasModern,
babelPresetPlugins: opts.babelPresetPlugins,
development: opts.development,
hasReactRefresh: opts.hasReactRefresh,
hasJsxRuntime: opts.hasJsxRuntime,
}
const filename = join(opts.cwd, 'noop.js')
const loader = Object.assign(
opts.cache
? {
cacheCompression: false,
cacheDirectory: join(opts.distDir, 'cache', 'next-babel-loader'),
cacheIdentifier:
cacheKey +
(opts.isServer ? '-server' : '') +
(opts.isModern ? '-modern' : '') +
(opts.hasModern ? '-has-modern' : '') +
'-new-polyfills' +
(opts.development ? '-development' : '-production') +
(opts.hasReactRefresh ? '-react-refresh' : '') +
(opts.hasJsxRuntime ? '-jsx-runtime' : '') +
JSON.stringify(
babel.loadPartialConfig({
filename,
cwd: opts.cwd,
sourceFileName: filename,
}).options
),
}
: {
cacheDirectory: false,
},
opts
)
delete loader.isServer
delete loader.cache
delete loader.distDir
delete loader.isModern
delete loader.hasModern
delete loader.pagesDir
delete loader.babelPresetPlugins
delete loader.development
delete loader.hasReactRefresh
delete loader.hasJsxRuntime
return { loader, custom }
},
config(
cfg,
{
source,
customOptions: {
isServer,
isModern,
hasModern,
pagesDir,
babelPresetPlugins,
development,
hasReactRefresh,
hasJsxRuntime,
},
}
) {
const filename = this.resourcePath
const options = Object.assign({}, cfg.options)
const isPageFile = filename.startsWith(pagesDir)
if (cfg.hasFilesystemConfig()) {
for (const file of [cfg.babelrc, cfg.config]) {
// We only log for client compilation otherwise there will be double output
if (file && !isServer && !configs.has(file)) {
configs.add(file)
Log.info(`Using external babel configuration from ${file}`)
}
}
} else {
// Add our default preset if the no "babelrc" found.
options.presets = [...options.presets, presetItem]
}
options.caller.isServer = isServer
options.caller.isModern = isModern
options.caller.isDev = development
options.caller.hasJsxRuntime = hasJsxRuntime
const emitWarning = this.emitWarning.bind(this)
Object.defineProperty(options.caller, 'onWarning', {
enumerable: false,
writable: false,
value: (options.caller.onWarning = function (reason) {
if (!(reason instanceof Error)) {
reason = new Error(reason)
}
emitWarning(reason)
}),
})
options.plugins = options.plugins || []
if (hasReactRefresh) {
const reactRefreshPlugin = babel.createConfigItem(
[require('react-refresh/babel'), { skipEnvCheck: true }],
{ type: 'plugin' }
)
options.plugins.unshift(reactRefreshPlugin)
if (!isServer) {
const noAnonymousDefaultExportPlugin = babel.createConfigItem(
[require('../../babel/plugins/no-anonymous-default-export'), {}],
{ type: 'plugin' }
)
options.plugins.unshift(noAnonymousDefaultExportPlugin)
}
}
if (!isServer && isPageFile) {
const pageConfigPlugin = babel.createConfigItem(
[require('../../babel/plugins/next-page-config')],
{ type: 'plugin' }
)
options.plugins.push(pageConfigPlugin)
const diallowExportAll = babel.createConfigItem(
[
require('../../babel/plugins/next-page-disallow-re-export-all-exports'),
],
{ type: 'plugin' }
)
options.plugins.push(diallowExportAll)
}
if (isServer && source.indexOf('next/data') !== -1) {
const nextDataPlugin = babel.createConfigItem(
[
require('../../babel/plugins/next-data'),
{ key: basename(filename) + '-' + hash(filename) },
],
{ type: 'plugin' }
)
options.plugins.push(nextDataPlugin)
}
if (isModern) {
const nextPreset = options.presets.find(
(preset) => preset && preset.value === nextBabelPreset
) || { options: {} }
const additionalPresets = options.presets.filter(
(preset) => preset !== nextPreset
)
const presetItemModern = babel.createConfigItem(
nextBabelPresetModern(nextPreset.options),
{
type: 'preset',
}
)
options.presets = [...additionalPresets, presetItemModern]
}
// If the file has `module.exports` we have to transpile commonjs because Babel adds `import` statements
// That break webpack, since webpack doesn't support combining commonjs and esmodules
if (!hasModern && source.indexOf('module.exports') !== -1) {
options.plugins.push(applyCommonJs)
}
options.plugins.push([
require.resolve('babel-plugin-transform-define'),
{
'process.env.NODE_ENV': development ? 'development' : 'production',
'typeof window': isServer ? 'undefined' : 'object',
'process.browser': isServer ? false : true,
},
'next-js-transform-define-instance',
])
if (isPageFile) {
if (!isServer) {
options.plugins.push([
require.resolve('../../babel/plugins/next-ssg-transform'),
{},
])
}
}
// As next-server/lib has stateful modules we have to transpile commonjs
options.overrides = [
...(options.overrides || []),
{
test: [
/next[\\/]dist[\\/]next-server[\\/]lib/,
/next[\\/]dist[\\/]client/,
/next[\\/]dist[\\/]pages/,
],
plugins: [commonJsItem],
},
]
for (const plugin of babelPresetPlugins) {
require(join(plugin.dir, 'src', 'babel-preset-build.js'))(
options,
plugin.config || {}
)
}
return options
},
}
})