mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-22 13:45:18 +00:00
feat: use webpack esm server build (#474)
This commit is contained in:
parent
7527d30bed
commit
193d7bf8bc
@ -7,6 +7,7 @@ import { getNitroContext, NitroContext } from './context'
|
||||
import { createDevServer } from './server/dev'
|
||||
import { wpfs } from './utils/wpfs'
|
||||
import { resolveMiddleware } from './server/middleware'
|
||||
import AsyncLoadingPlugin from './webpack/wp4'
|
||||
|
||||
export default function nuxt2CompatModule (this: ModuleContainer) {
|
||||
const { nuxt } = this
|
||||
@ -64,6 +65,13 @@ export default function nuxt2CompatModule (this: ModuleContainer) {
|
||||
}
|
||||
})
|
||||
|
||||
// Set up webpack plugin for node async loading
|
||||
nuxt.hook('webpack:config', (webpackConfigs) => {
|
||||
const serverConfig = webpackConfigs.find(config => config.name === 'server')
|
||||
serverConfig.plugins = serverConfig.plugins || []
|
||||
serverConfig.plugins.push(new AsyncLoadingPlugin())
|
||||
})
|
||||
|
||||
// Nitro client plugin
|
||||
this.addPlugin({
|
||||
fileName: 'nitro.client.mjs',
|
||||
|
@ -161,14 +161,13 @@ export const getRollupConfig = (nitroContext: NitroContext) => {
|
||||
rollupConfig.plugins.push(dynamicRequire({
|
||||
dir: resolve(nitroContext._nuxt.buildDir, 'dist/server'),
|
||||
inline: nitroContext.node === false || nitroContext.inlineDynamicImports,
|
||||
globbyOptions: {
|
||||
ignore: [
|
||||
'client.manifest.mjs',
|
||||
'server.cjs',
|
||||
'server.mjs',
|
||||
'server.manifest.mjs'
|
||||
]
|
||||
}
|
||||
ignore: [
|
||||
'client.manifest.mjs',
|
||||
'server.js',
|
||||
'server.cjs',
|
||||
'server.mjs',
|
||||
'server.manifest.mjs'
|
||||
]
|
||||
}))
|
||||
|
||||
// Assets
|
||||
|
@ -1,15 +1,15 @@
|
||||
import { resolve } from 'upath'
|
||||
import globby, { GlobbyOptions } from 'globby'
|
||||
import globby from 'globby'
|
||||
import type { Plugin } from 'rollup'
|
||||
|
||||
const PLUGIN_NAME = 'dynamic-require'
|
||||
const HELPER_DYNAMIC = `\0${PLUGIN_NAME}.js`
|
||||
const DYNAMIC_REQUIRE_RE = /require\("\.\/" ?\+/g
|
||||
const DYNAMIC_REQUIRE_RE = /import\("\.\/" ?\+(.*)\).then/g
|
||||
|
||||
interface Options {
|
||||
dir: string
|
||||
inline: boolean
|
||||
globbyOptions: GlobbyOptions
|
||||
ignore: string[]
|
||||
outDir?: string
|
||||
prefix?: string
|
||||
}
|
||||
@ -29,19 +29,19 @@ interface TemplateContext {
|
||||
chunks: Chunk[]
|
||||
}
|
||||
|
||||
export function dynamicRequire ({ dir, globbyOptions, inline }: Options): Plugin {
|
||||
export function dynamicRequire ({ dir, ignore, inline }: Options): Plugin {
|
||||
return {
|
||||
name: PLUGIN_NAME,
|
||||
transform (code: string, _id: string) {
|
||||
return {
|
||||
code: code.replace(DYNAMIC_REQUIRE_RE, `require('${HELPER_DYNAMIC}')(`),
|
||||
code: code.replace(DYNAMIC_REQUIRE_RE, `import('${HELPER_DYNAMIC}').then(r => r.default || r).then(dynamicRequire => dynamicRequire($1)).then`),
|
||||
map: null
|
||||
}
|
||||
},
|
||||
resolveId (id: string) {
|
||||
return id === HELPER_DYNAMIC ? id : null
|
||||
},
|
||||
// TODO: Async chunk loading over netwrok!
|
||||
// TODO: Async chunk loading over network!
|
||||
// renderDynamicImport () {
|
||||
// return {
|
||||
// left: 'fetch(', right: ')'
|
||||
@ -53,7 +53,13 @@ export function dynamicRequire ({ dir, globbyOptions, inline }: Options): Plugin
|
||||
}
|
||||
|
||||
// Scan chunks
|
||||
const files = await globby('**/*.{cjs,mjs,js}', { cwd: dir, absolute: false, ...globbyOptions })
|
||||
let files = []
|
||||
try {
|
||||
const wpManifest = resolve(dir, './server.manifest.json')
|
||||
files = await import(wpManifest).then(r => Object.keys(r.files).filter(file => !ignore.includes(file)))
|
||||
} catch {
|
||||
files = await globby('**/*.{cjs,mjs,js}', { cwd: dir, absolute: false, ignore })
|
||||
}
|
||||
const chunks = files.map(id => ({
|
||||
id,
|
||||
src: resolve(dir, id).replace(/\\/g, '/'),
|
||||
@ -62,20 +68,6 @@ export function dynamicRequire ({ dir, globbyOptions, inline }: Options): Plugin
|
||||
}))
|
||||
|
||||
return inline ? TMPL_INLINE({ chunks }) : TMPL_LAZY({ chunks })
|
||||
},
|
||||
renderChunk (code) {
|
||||
if (inline) {
|
||||
return {
|
||||
map: null,
|
||||
code
|
||||
}
|
||||
}
|
||||
return {
|
||||
map: null,
|
||||
code: code.replace(
|
||||
/Promise.resolve\(\).then\(function \(\) \{ return require\('([^']*)' \/\* webpackChunk \*\/\); \}\).then\(function \(n\) \{ return n.([_a-zA-Z0-9]*); \}\)/g,
|
||||
"require('$1').$2")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -91,47 +83,20 @@ function getWebpackChunkMeta (src: string) {
|
||||
}
|
||||
|
||||
function TMPL_INLINE ({ chunks }: TemplateContext) {
|
||||
return `${chunks.map(i => `import ${i.name} from '${i.src}'`).join('\n')}
|
||||
return `${chunks.map(i => `import * as ${i.name} from '${i.src}'`).join('\n')}
|
||||
const dynamicChunks = {
|
||||
${chunks.map(i => ` ['${i.id}']: ${i.name}`).join(',\n')}
|
||||
};
|
||||
|
||||
export default function dynamicRequire(id) {
|
||||
return dynamicChunks[id];
|
||||
return Promise.resolve(dynamicChunks[id]);
|
||||
};`
|
||||
}
|
||||
|
||||
function TMPL_LAZY ({ chunks }: TemplateContext) {
|
||||
return `
|
||||
function dynamicWebpackModule(id, getChunk, ids) {
|
||||
return function (module, exports, require) {
|
||||
const r = getChunk()
|
||||
if (typeof r.then === 'function') {
|
||||
module.exports = r.then(r => {
|
||||
const realModule = { exports: {}, require };
|
||||
r.modules[id](realModule, realModule.exports, realModule.require);
|
||||
for (const _id of ids) {
|
||||
if (_id === id) continue;
|
||||
r.modules[_id](realModule, realModule.exports, realModule.require);
|
||||
}
|
||||
return realModule.exports;
|
||||
});
|
||||
} else if (r && typeof r.modules[id] === 'function') {
|
||||
r.modules[id](module, exports, require);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
function webpackChunk (meta, getChunk) {
|
||||
const chunk = { ...meta, modules: {} };
|
||||
for (const id of meta.moduleIds) {
|
||||
chunk.modules[id] = dynamicWebpackModule(id, getChunk, meta.moduleIds);
|
||||
};
|
||||
return chunk;
|
||||
};
|
||||
|
||||
const dynamicChunks = {
|
||||
${chunks.map(i => ` ['${i.id}']: () => webpackChunk(${JSON.stringify(i.meta)}, () => import('${i.src}' /* webpackChunk */))`).join(',\n')}
|
||||
${chunks.map(i => ` ['${i.id}']: () => import('${i.src}')`).join(',\n')}
|
||||
};
|
||||
|
||||
export default function dynamicRequire(id) {
|
||||
|
132
packages/nitro/src/webpack/wp4.ts
Normal file
132
packages/nitro/src/webpack/wp4.ts
Normal file
@ -0,0 +1,132 @@
|
||||
// Based on https://github.com/webpack/webpack/blob/v4.46.0/lib/node/NodeMainTemplatePlugin.js#L81-L191
|
||||
|
||||
import { Compiler } from 'webpack'
|
||||
import Template from 'webpack/lib/Template'
|
||||
|
||||
export default class AsyncLoadingPlugin {
|
||||
apply (compiler: Compiler) {
|
||||
compiler.hooks.compilation.tap('AsyncLoading', (compilation) => {
|
||||
const mainTemplate = compilation.mainTemplate
|
||||
mainTemplate.hooks.requireEnsure.tap(
|
||||
'AsyncLoading',
|
||||
(_source, chunk, hash) => {
|
||||
const chunkFilename = mainTemplate.outputOptions.chunkFilename
|
||||
const chunkMaps = chunk.getChunkMaps()
|
||||
const insertMoreModules = [
|
||||
'var moreModules = chunk.modules, chunkIds = chunk.ids;',
|
||||
'for(var moduleId in moreModules) {',
|
||||
Template.indent(
|
||||
mainTemplate.renderAddModule(
|
||||
hash,
|
||||
chunk,
|
||||
'moduleId',
|
||||
'moreModules[moduleId]'
|
||||
)
|
||||
),
|
||||
'}'
|
||||
]
|
||||
return Template.asString([
|
||||
'// Async chunk loading for Nitro',
|
||||
'',
|
||||
'var installedChunkData = installedChunks[chunkId];',
|
||||
'if(installedChunkData !== 0) { // 0 means "already installed".',
|
||||
Template.indent([
|
||||
'// array of [resolve, reject, promise] means "currently loading"',
|
||||
'if(installedChunkData) {',
|
||||
Template.indent(['promises.push(installedChunkData[2]);']),
|
||||
'} else {',
|
||||
Template.indent([
|
||||
'// load the chunk and return promise to it',
|
||||
'var promise = new Promise(function(resolve, reject) {',
|
||||
Template.indent([
|
||||
'installedChunkData = installedChunks[chunkId] = [resolve, reject];',
|
||||
'import(' +
|
||||
mainTemplate.getAssetPath(
|
||||
JSON.stringify(`./${chunkFilename}`),
|
||||
{
|
||||
hash: `" + ${mainTemplate.renderCurrentHashCode(
|
||||
hash
|
||||
)} + "`,
|
||||
hashWithLength: length =>
|
||||
`" + ${mainTemplate.renderCurrentHashCode(
|
||||
hash,
|
||||
length
|
||||
)} + "`,
|
||||
chunk: {
|
||||
id: '" + chunkId + "',
|
||||
hash: `" + ${JSON.stringify(
|
||||
chunkMaps.hash
|
||||
)}[chunkId] + "`,
|
||||
hashWithLength: (length) => {
|
||||
const shortChunkHashMap = {}
|
||||
for (const chunkId of Object.keys(chunkMaps.hash)) {
|
||||
if (typeof chunkMaps.hash[chunkId] === 'string') {
|
||||
shortChunkHashMap[chunkId] = chunkMaps.hash[
|
||||
chunkId
|
||||
].substr(0, length)
|
||||
}
|
||||
}
|
||||
return `" + ${JSON.stringify(
|
||||
shortChunkHashMap
|
||||
)}[chunkId] + "`
|
||||
},
|
||||
contentHash: {
|
||||
javascript: `" + ${JSON.stringify(
|
||||
chunkMaps.contentHash.javascript
|
||||
)}[chunkId] + "`
|
||||
},
|
||||
contentHashWithLength: {
|
||||
javascript: (length) => {
|
||||
const shortContentHashMap = {}
|
||||
const contentHash =
|
||||
chunkMaps.contentHash.javascript
|
||||
for (const chunkId of Object.keys(contentHash)) {
|
||||
if (typeof contentHash[chunkId] === 'string') {
|
||||
shortContentHashMap[chunkId] = contentHash[
|
||||
chunkId
|
||||
].substr(0, length)
|
||||
}
|
||||
}
|
||||
return `" + ${JSON.stringify(
|
||||
shortContentHashMap
|
||||
)}[chunkId] + "`
|
||||
}
|
||||
},
|
||||
name: `" + (${JSON.stringify(
|
||||
chunkMaps.name
|
||||
)}[chunkId]||chunkId) + "`
|
||||
},
|
||||
contentHashType: 'javascript'
|
||||
}
|
||||
) +
|
||||
').then(chunk => {',
|
||||
Template.indent(
|
||||
insertMoreModules
|
||||
.concat([
|
||||
'var callbacks = [];',
|
||||
'for(var i = 0; i < chunkIds.length; i++) {',
|
||||
Template.indent([
|
||||
'if(installedChunks[chunkIds[i]])',
|
||||
Template.indent([
|
||||
'callbacks = callbacks.concat(installedChunks[chunkIds[i]][0]);'
|
||||
]),
|
||||
'installedChunks[chunkIds[i]] = 0;'
|
||||
]),
|
||||
'}',
|
||||
'for(i = 0; i < callbacks.length; i++)',
|
||||
Template.indent('callbacks[i]();')
|
||||
])
|
||||
),
|
||||
'});'
|
||||
]),
|
||||
'});',
|
||||
'promises.push(installedChunkData[2] = promise);'
|
||||
]),
|
||||
'}'
|
||||
]),
|
||||
'}'
|
||||
])
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
@ -40,7 +40,7 @@
|
||||
"vue": "3.2.2",
|
||||
"vue-loader": "^16.5.0",
|
||||
"vue-style-loader": "^4.1.3",
|
||||
"webpack": "^5.50.0",
|
||||
"webpack": "^5.52.0",
|
||||
"webpack-bundle-analyzer": "^4.4.2",
|
||||
"webpack-dev-middleware": "^5.0.0",
|
||||
"webpack-hot-middleware": "^2.25.0",
|
||||
|
@ -22,7 +22,7 @@ export function server (ctx: WebpackConfigContext) {
|
||||
function serverPreset (ctx: WebpackConfigContext) {
|
||||
const { config } = ctx
|
||||
|
||||
config.output.filename = 'server.cjs'
|
||||
config.output.filename = 'server.mjs'
|
||||
config.devtool = 'cheap-module-source-map'
|
||||
|
||||
config.optimization = {
|
||||
|
@ -81,14 +81,6 @@ export default class VueSSRServerPlugin {
|
||||
size: () => mjsSrc.length
|
||||
}
|
||||
|
||||
// TODO: Workaround for webpack
|
||||
const serverJS = 'export { default } from "./server.cjs"'
|
||||
assets['server.mjs'] = {
|
||||
source: () => serverJS,
|
||||
map: () => null,
|
||||
size: () => serverJS.length
|
||||
}
|
||||
|
||||
cb()
|
||||
})
|
||||
})
|
||||
|
@ -10,11 +10,6 @@ export const validate = (compiler) => {
|
||||
consola.warn('webpack config `target` should be "node".')
|
||||
}
|
||||
|
||||
const libraryType = compiler.options.output.library.type
|
||||
if (libraryType !== 'commonjs2') {
|
||||
consola.warn('webpack config `output.libraryTarget` should be "commonjs2".')
|
||||
}
|
||||
|
||||
if (!compiler.options.externals) {
|
||||
consola.info(
|
||||
'It is recommended to externalize dependencies in the server build for ' +
|
||||
|
@ -31,6 +31,7 @@ function baseConfig (ctx: WebpackConfigContext) {
|
||||
...options.build.optimization as any,
|
||||
minimizer: []
|
||||
},
|
||||
experiments: {},
|
||||
mode: ctx.isDev ? 'development' : 'production',
|
||||
cache: getCache(ctx),
|
||||
output: getOutput(ctx),
|
||||
|
@ -7,7 +7,7 @@ export function esbuild (ctx: WebpackConfigContext) {
|
||||
// https://esbuild.github.io/getting-started/#bundling-for-the-browser
|
||||
// https://gs.statcounter.com/browser-version-market-share
|
||||
// https://nodejs.org/en/
|
||||
const target = ctx.isServer ? 'node14' : 'chrome85'
|
||||
const target = ctx.isServer ? 'es2019' : 'chrome85'
|
||||
|
||||
config.optimization.minimizer.push(new ESBuildMinifyPlugin())
|
||||
|
||||
|
@ -6,13 +6,25 @@ export function node (ctx: WebpackConfigContext) {
|
||||
config.target = 'node'
|
||||
config.node = false
|
||||
|
||||
config.resolve.mainFields = ['main', 'module']
|
||||
config.experiments.outputModule = true
|
||||
|
||||
config.output = {
|
||||
...config.output,
|
||||
chunkFilename: '[name].cjs',
|
||||
chunkFilename: '[name].mjs',
|
||||
chunkFormat: 'module',
|
||||
chunkLoading: 'import',
|
||||
module: true,
|
||||
environment: {
|
||||
module: true,
|
||||
arrowFunction: true,
|
||||
bigIntLiteral: true,
|
||||
const: true,
|
||||
destructuring: true,
|
||||
dynamicImport: true,
|
||||
forOf: true
|
||||
},
|
||||
library: {
|
||||
type: 'commonjs2'
|
||||
type: 'module'
|
||||
}
|
||||
}
|
||||
|
||||
|
10
yarn.lock
10
yarn.lock
@ -1456,7 +1456,7 @@ __metadata:
|
||||
vue: 3.2.2
|
||||
vue-loader: ^16.5.0
|
||||
vue-style-loader: ^4.1.3
|
||||
webpack: ^5.50.0
|
||||
webpack: ^5.52.0
|
||||
webpack-bundle-analyzer: ^4.4.2
|
||||
webpack-dev-middleware: ^5.0.0
|
||||
webpack-hot-middleware: ^2.25.0
|
||||
@ -12195,9 +12195,9 @@ typescript@^4.3.5:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"webpack@npm:^5, webpack@npm:^5.1.0, webpack@npm:^5.38.1, webpack@npm:^5.50.0":
|
||||
version: 5.50.0
|
||||
resolution: "webpack@npm:5.50.0"
|
||||
"webpack@npm:^5, webpack@npm:^5.1.0, webpack@npm:^5.38.1, webpack@npm:^5.52.0":
|
||||
version: 5.52.0
|
||||
resolution: "webpack@npm:5.52.0"
|
||||
dependencies:
|
||||
"@types/eslint-scope": ^3.7.0
|
||||
"@types/estree": ^0.0.50
|
||||
@ -12228,7 +12228,7 @@ typescript@^4.3.5:
|
||||
optional: true
|
||||
bin:
|
||||
webpack: bin/webpack.js
|
||||
checksum: 293bed1d9101ac127605f35a225a5cbc1bc89eac68d6e09e7feb3e284ec2693b3db7c1dd7710fadf6852f89ad39ed09413c35befa1cfc9738074b33299ac2b9e
|
||||
checksum: fe7cbb761b251a6885d67971f8763a9675ca4777ff863be4cbe76a6ab22a3f810be2728fe7b9c31f74259001859a3915ad581f0e4aca5255cdb13ccab3472f00
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user