8ff22ca398
Re-do of https://github.com/vercel/next.js/pull/40251 Edge SSR'd routes cold boot performances are proportional to the executed code size. In order to improve it, we are trying to optimize for the bundle size of a packed Edge SSR route. This PR adds ESM compilation targets for all Next.js dist packages and use them to bundle Edge SSR'd route. This allows us to leverage the better tree shaking/DCE for ESM modules in webpack in order to decrease the overall bundle size. This PR also enables minifying Edge SSR routes. Since we don't control which minifier might be used later (if any), it's best if we provide an already optimised bundle. <img width="903" alt="image" src="https://user-images.githubusercontent.com/11064311/190005211-b7cb2c58-a56a-44b0-8ee4-fd3f603e41bd.png"> This is a 10ms cold boot win per my benchmarking script, which I'll put in a subsequent PR. Not done yet: - ~~swap exported requires in `next/link` (and others) etc to point them to the esm modules version~~ <!-- Thanks for opening a PR! Your contribution is much appreciated. In order to make sure your PR is handled as smoothly as possible we request that you follow the checklist sections below. Choose the right checklist for the change that you're making: --> - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Errors have helpful link attached, see `contributing.md` - [x] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR. - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Documentation added - [ ] Telemetry added. In case of a feature if it's used or not. - [ ] Errors have helpful link attached, see `contributing.md` - [ ] Make sure the linting passes by running `pnpm lint` - [ ] The examples guidelines are followed from [our contributing doc](https://github.com/vercel/next.js/blob/canary/contributing.md#adding-examples) Co-authored-by: JJ Kasper <jj@jjsweb.site> Co-authored-by: Shu Ding <g@shud.in> <!-- Thanks for opening a PR! Your contribution is much appreciated. To make sure your PR is handled as smoothly as possible we request that you follow the checklist sections below. Choose the right checklist for the change that you're making: --> ## Bug - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Errors have a helpful link attached, see `contributing.md` ## Feature - [ ] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR. - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Documentation added - [ ] Telemetry added. In case of a feature if it's used or not. - [ ] Errors have a helpful link attached, see `contributing.md` ## Documentation / Examples - [ ] Make sure the linting passes by running `pnpm lint` - [ ] The "examples guidelines" are followed from [our contributing doc](https://github.com/vercel/next.js/blob/canary/contributing/examples/adding-examples.md) Co-authored-by: JJ Kasper <jj@jjsweb.site> Co-authored-by: Shu Ding <g@shud.in>
162 lines
4.6 KiB
JavaScript
162 lines
4.6 KiB
JavaScript
// taskr babel plugin with Babel 7 support
|
|
// https://github.com/lukeed/taskr/pull/305
|
|
|
|
const path = require('path')
|
|
|
|
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
const transform = require('@swc/core').transform
|
|
|
|
module.exports = function (task) {
|
|
// eslint-disable-next-line require-yield
|
|
task.plugin(
|
|
'swc',
|
|
{},
|
|
function* (
|
|
file,
|
|
serverOrClient,
|
|
{
|
|
stripExtension,
|
|
keepImportAssertions = false,
|
|
interopClientDefaultExport = false,
|
|
esm = false,
|
|
} = {}
|
|
) {
|
|
// Don't compile .d.ts
|
|
if (file.base.endsWith('.d.ts')) return
|
|
|
|
const isClient = serverOrClient === 'client'
|
|
|
|
/** @type {import('@swc/core').Options} */
|
|
const swcClientOptions = {
|
|
module: {
|
|
type: esm ? 'es6' : 'commonjs',
|
|
ignoreDynamic: true,
|
|
},
|
|
jsc: {
|
|
loose: true,
|
|
externalHelpers: true,
|
|
target: 'es2016',
|
|
parser: {
|
|
syntax: 'typescript',
|
|
dynamicImport: true,
|
|
importAssertions: true,
|
|
tsx: file.base.endsWith('.tsx'),
|
|
},
|
|
experimental: {
|
|
keepImportAssertions,
|
|
},
|
|
transform: {
|
|
react: {
|
|
pragma: 'React.createElement',
|
|
pragmaFrag: 'React.Fragment',
|
|
throwIfNamespace: true,
|
|
development: false,
|
|
useBuiltins: true,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
/** @type {import('@swc/core').Options} */
|
|
const swcServerOptions = {
|
|
module: {
|
|
type: esm ? 'es6' : 'commonjs',
|
|
ignoreDynamic: true,
|
|
},
|
|
env: {
|
|
targets: {
|
|
node: '12.0.0',
|
|
},
|
|
},
|
|
jsc: {
|
|
loose: true,
|
|
// Do not enable external helpers on server-side files build
|
|
// _is_native_funtion helper is not compatible with edge runtime (need investigate)
|
|
externalHelpers: false,
|
|
parser: {
|
|
syntax: 'typescript',
|
|
dynamicImport: true,
|
|
importAssertions: true,
|
|
tsx: file.base.endsWith('.tsx'),
|
|
},
|
|
experimental: {
|
|
keepImportAssertions,
|
|
},
|
|
transform: {
|
|
react: {
|
|
pragma: 'React.createElement',
|
|
pragmaFrag: 'React.Fragment',
|
|
throwIfNamespace: true,
|
|
development: false,
|
|
useBuiltins: true,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
const swcOptions = isClient ? swcClientOptions : swcServerOptions
|
|
|
|
const filePath = path.join(file.dir, file.base)
|
|
const fullFilePath = path.join(__dirname, filePath)
|
|
const distFilePath = path.dirname(path.join(__dirname, 'dist', filePath))
|
|
|
|
const options = {
|
|
filename: path.join(file.dir, file.base),
|
|
sourceMaps: true,
|
|
inlineSourcesContent: false,
|
|
sourceFileName: path.relative(distFilePath, fullFilePath),
|
|
|
|
...swcOptions,
|
|
}
|
|
|
|
const source = file.data.toString('utf-8')
|
|
const output = yield transform(source, options)
|
|
const ext = path.extname(file.base)
|
|
|
|
// Make sure the output content keeps the `"client"` directive.
|
|
// TODO: Remove this once SWC fixes the issue.
|
|
if (/^['"]client['"]/.test(source)) {
|
|
output.code = '"client";\n' + output.code
|
|
}
|
|
|
|
// Replace `.ts|.tsx` with `.js` in files with an extension
|
|
if (ext) {
|
|
const extRegex = new RegExp(ext.replace('.', '\\.') + '$', 'i')
|
|
// Remove the extension if stripExtension is enabled or replace it with `.js`
|
|
file.base = file.base.replace(extRegex, stripExtension ? '' : '.js')
|
|
}
|
|
|
|
if (output.map) {
|
|
if (interopClientDefaultExport && !esm) {
|
|
output.code += `
|
|
if ((typeof exports.default === 'function' || (typeof exports.default === 'object' && exports.default !== null)) && typeof exports.default.__esModule === 'undefined') {
|
|
Object.defineProperty(exports.default, '__esModule', { value: true });
|
|
Object.assign(exports.default, exports);
|
|
module.exports = exports.default;
|
|
}
|
|
`
|
|
}
|
|
|
|
const map = `${file.base}.map`
|
|
|
|
output.code += Buffer.from(`\n//# sourceMappingURL=${map}`)
|
|
|
|
// add sourcemap to `files` array
|
|
this._.files.push({
|
|
base: map,
|
|
dir: file.dir,
|
|
data: Buffer.from(output.map),
|
|
})
|
|
}
|
|
|
|
file.data = Buffer.from(setNextVersion(output.code))
|
|
}
|
|
)
|
|
}
|
|
|
|
function setNextVersion(code) {
|
|
return code.replace(
|
|
/process\.env\.__NEXT_VERSION/g,
|
|
`"${require('./package.json').version}"`
|
|
)
|
|
}
|