2020-07-02 13:02:35 +00:00
|
|
|
import path from 'path'
|
|
|
|
import pify from 'pify'
|
|
|
|
import webpack from 'webpack'
|
|
|
|
import Glob from 'glob'
|
|
|
|
import webpackDevMiddleware from 'webpack-dev-middleware'
|
|
|
|
import webpackHotMiddleware from 'webpack-hot-middleware'
|
|
|
|
import consola from 'consola'
|
2020-08-18 20:33:58 +00:00
|
|
|
import { Nuxt } from 'src/core'
|
2021-01-20 14:43:43 +00:00
|
|
|
import { TARGETS, parallel, sequence, wrapArray } from 'src/utils'
|
2020-07-02 13:02:35 +00:00
|
|
|
import { createMFS } from './utils/mfs'
|
2020-09-02 12:27:27 +00:00
|
|
|
import { client, server } from './configs'
|
|
|
|
import { createWebpackConfigContext, applyPresets, getWebpackConfig } from './utils/config'
|
2020-07-02 13:02:35 +00:00
|
|
|
|
|
|
|
const glob = pify(Glob)
|
|
|
|
|
|
|
|
export class WebpackBundler {
|
2020-08-18 20:33:58 +00:00
|
|
|
nuxt: Nuxt
|
|
|
|
plugins: Array<string>
|
|
|
|
|
|
|
|
constructor (nuxt) {
|
|
|
|
this.nuxt = nuxt
|
|
|
|
// TODO: plugins
|
|
|
|
this.plugins = []
|
2020-07-02 13:02:35 +00:00
|
|
|
|
|
|
|
// Class fields
|
|
|
|
this.compilers = []
|
|
|
|
this.compilersWatching = []
|
|
|
|
this.devMiddleware = {}
|
|
|
|
this.hotMiddleware = {}
|
|
|
|
|
|
|
|
// Bind middleware to self
|
|
|
|
this.middleware = this.middleware.bind(this)
|
|
|
|
|
|
|
|
// Initialize shared MFS for dev
|
2020-08-18 20:33:58 +00:00
|
|
|
if (this.nuxt.options.dev) {
|
2020-07-02 13:02:35 +00:00
|
|
|
this.mfs = createMFS()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
getWebpackConfig (name) {
|
2020-09-02 12:27:27 +00:00
|
|
|
const ctx = createWebpackConfigContext({ nuxt: this.nuxt })
|
|
|
|
|
|
|
|
if (name === 'client') {
|
|
|
|
applyPresets(ctx, client)
|
|
|
|
} else if (name === 'server') {
|
|
|
|
applyPresets(ctx, server)
|
|
|
|
} else {
|
2020-07-02 13:02:35 +00:00
|
|
|
throw new Error(`Unsupported webpack config ${name}`)
|
|
|
|
}
|
2020-09-02 12:27:27 +00:00
|
|
|
|
|
|
|
return getWebpackConfig(ctx)
|
2020-07-02 13:02:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
async build () {
|
2020-08-18 20:33:58 +00:00
|
|
|
const { options } = this.nuxt
|
2020-07-02 13:02:35 +00:00
|
|
|
|
|
|
|
const webpackConfigs = [
|
2020-09-02 12:27:27 +00:00
|
|
|
this.getWebpackConfig('client')
|
2020-07-02 13:02:35 +00:00
|
|
|
]
|
|
|
|
|
|
|
|
if (options.modern) {
|
2020-09-02 12:27:27 +00:00
|
|
|
webpackConfigs.push(this.getWebpackConfig('modern'))
|
2020-07-02 13:02:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (options.build.ssr) {
|
2020-09-02 12:27:27 +00:00
|
|
|
webpackConfigs.push(this.getWebpackConfig('server'))
|
2020-07-02 13:02:35 +00:00
|
|
|
}
|
|
|
|
|
2020-08-18 20:33:58 +00:00
|
|
|
await this.nuxt.callHook('webpack:config', webpackConfigs)
|
2020-07-02 13:02:35 +00:00
|
|
|
|
|
|
|
// Check styleResource existence
|
2020-08-18 20:33:58 +00:00
|
|
|
const { styleResources } = this.nuxt.options.build
|
2020-07-02 13:02:35 +00:00
|
|
|
if (styleResources && Object.keys(styleResources).length) {
|
|
|
|
consola.warn(
|
|
|
|
'Using styleResources without the @nuxtjs/style-resources is not suggested and can lead to severe performance issues.',
|
|
|
|
'Please use https://github.com/nuxt-community/style-resources-module'
|
|
|
|
)
|
|
|
|
for (const ext of Object.keys(styleResources)) {
|
|
|
|
await Promise.all(wrapArray(styleResources[ext]).map(async (p) => {
|
2020-08-18 20:33:58 +00:00
|
|
|
const styleResourceFiles = await glob(path.resolve(this.nuxt.options.rootDir, p))
|
2020-07-02 13:02:35 +00:00
|
|
|
|
|
|
|
if (!styleResourceFiles || styleResourceFiles.length === 0) {
|
|
|
|
throw new Error(`Style Resource not found: ${p}`)
|
|
|
|
}
|
|
|
|
}))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Configure compilers
|
|
|
|
this.compilers = webpackConfigs.map((config) => {
|
|
|
|
const compiler = webpack(config)
|
|
|
|
|
|
|
|
// In dev, write files in memory FS
|
|
|
|
if (options.dev) {
|
|
|
|
compiler.outputFileSystem = this.mfs
|
|
|
|
}
|
|
|
|
|
|
|
|
return compiler
|
|
|
|
})
|
|
|
|
|
|
|
|
// Start Builds
|
|
|
|
const runner = options.dev ? parallel : sequence
|
|
|
|
|
|
|
|
await runner(this.compilers, compiler => this.webpackCompile(compiler))
|
|
|
|
}
|
|
|
|
|
|
|
|
async webpackCompile (compiler) {
|
|
|
|
const { name } = compiler.options
|
2020-08-18 20:33:58 +00:00
|
|
|
const { options } = this.nuxt
|
2020-07-02 13:02:35 +00:00
|
|
|
|
2020-08-18 20:33:58 +00:00
|
|
|
await this.nuxt.callHook('build:compile', { name, compiler })
|
2020-07-02 13:02:35 +00:00
|
|
|
|
|
|
|
// Load renderer resources after build
|
|
|
|
compiler.hooks.done.tap('load-resources', async (stats) => {
|
2020-08-18 20:33:58 +00:00
|
|
|
await this.nuxt.callHook('build:compiled', {
|
2020-07-02 13:02:35 +00:00
|
|
|
name,
|
|
|
|
compiler,
|
|
|
|
stats
|
|
|
|
})
|
|
|
|
|
|
|
|
// Reload renderer
|
2020-08-18 20:33:58 +00:00
|
|
|
await this.nuxt.callHook('build:resources', this.mfs)
|
2020-07-02 13:02:35 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
// --- Dev Build ---
|
|
|
|
if (options.dev) {
|
2020-09-02 12:27:27 +00:00
|
|
|
// Client build
|
2020-07-02 13:02:35 +00:00
|
|
|
if (['client', 'modern'].includes(name)) {
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
compiler.hooks.done.tap('nuxt-dev', () => { resolve() })
|
|
|
|
compiler.hooks.failed.tap('nuxt-errorlog', (err) => { reject(err) })
|
|
|
|
// Start watch
|
|
|
|
this.webpackDev(compiler)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// Server, build and watch for changes
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
const watching = compiler.watch(options.watchers.webpack, (err) => {
|
|
|
|
if (err) {
|
|
|
|
return reject(err)
|
|
|
|
}
|
|
|
|
resolve()
|
|
|
|
})
|
|
|
|
|
|
|
|
watching.close = pify(watching.close)
|
|
|
|
this.compilersWatching.push(watching)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// --- Production Build ---
|
|
|
|
compiler.run = pify(compiler.run)
|
|
|
|
const stats = await compiler.run()
|
|
|
|
|
|
|
|
if (stats.hasErrors()) {
|
|
|
|
// non-quiet mode: errors will be printed by webpack itself
|
|
|
|
const error = new Error('Nuxt build error')
|
|
|
|
if (options.build.quiet === true) {
|
|
|
|
error.stack = stats.toString('errors-only')
|
|
|
|
}
|
|
|
|
throw error
|
|
|
|
}
|
|
|
|
|
|
|
|
// Await for renderer to load resources (programmatic, tests and generate)
|
2020-08-18 20:33:58 +00:00
|
|
|
await this.nuxt.callHook('build:resources')
|
2020-07-02 13:02:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
async webpackDev (compiler) {
|
|
|
|
consola.debug('Creating webpack middleware...')
|
|
|
|
|
|
|
|
const { name } = compiler.options
|
2020-08-18 20:33:58 +00:00
|
|
|
const buildOptions = this.nuxt.options.build
|
2020-07-02 13:02:35 +00:00
|
|
|
const { client, ...hotMiddlewareOptions } = buildOptions.hotMiddleware || {}
|
|
|
|
|
|
|
|
// Create webpack dev middleware
|
|
|
|
this.devMiddleware[name] = pify(
|
|
|
|
webpackDevMiddleware(
|
|
|
|
compiler, {
|
|
|
|
publicPath: buildOptions.publicPath,
|
2020-10-29 17:05:29 +00:00
|
|
|
outputFileSystem: this.mfs,
|
2020-07-02 13:02:35 +00:00
|
|
|
...buildOptions.devMiddleware
|
|
|
|
})
|
|
|
|
)
|
|
|
|
|
|
|
|
this.devMiddleware[name].close = pify(this.devMiddleware[name].close)
|
|
|
|
|
|
|
|
this.compilersWatching.push(this.devMiddleware[name].context.watching)
|
|
|
|
|
|
|
|
this.hotMiddleware[name] = pify(
|
|
|
|
webpackHotMiddleware(
|
|
|
|
compiler, {
|
|
|
|
log: false,
|
|
|
|
heartbeat: 10000,
|
|
|
|
path: `/__webpack_hmr/${name}`,
|
|
|
|
...hotMiddlewareOptions
|
|
|
|
})
|
|
|
|
)
|
|
|
|
|
|
|
|
// Register devMiddleware on server
|
2020-08-18 20:33:58 +00:00
|
|
|
await this.nuxt.callHook('server:devMiddleware', this.middleware)
|
2020-07-02 13:02:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
async middleware (req, res, next) {
|
2021-01-20 14:43:43 +00:00
|
|
|
if (this.devMiddleware && this.devMiddleware.client) {
|
|
|
|
await this.devMiddleware.client(req, res)
|
2020-07-02 13:02:35 +00:00
|
|
|
}
|
|
|
|
|
2021-01-20 14:43:43 +00:00
|
|
|
if (this.hotMiddleware && this.hotMiddleware.client) {
|
|
|
|
await this.hotMiddleware.client(req, res)
|
2020-07-02 13:02:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
next()
|
|
|
|
}
|
|
|
|
|
|
|
|
async unwatch () {
|
|
|
|
await Promise.all(this.compilersWatching.map(watching => watching.close()))
|
|
|
|
}
|
|
|
|
|
|
|
|
async close () {
|
|
|
|
if (this.__closed) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
this.__closed = true
|
|
|
|
|
|
|
|
// Unwatch
|
|
|
|
await this.unwatch()
|
|
|
|
|
|
|
|
// Stop webpack middleware
|
|
|
|
for (const devMiddleware of Object.values(this.devMiddleware)) {
|
|
|
|
await devMiddleware.close()
|
|
|
|
}
|
|
|
|
|
|
|
|
for (const compiler of this.compilers) {
|
|
|
|
compiler.close()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Cleanup MFS
|
|
|
|
if (this.mfs) {
|
|
|
|
delete this.mfs.data
|
|
|
|
delete this.mfs
|
|
|
|
}
|
|
|
|
|
|
|
|
// Cleanup more resources
|
|
|
|
delete this.compilers
|
|
|
|
delete this.compilersWatching
|
|
|
|
delete this.devMiddleware
|
|
|
|
delete this.hotMiddleware
|
|
|
|
}
|
|
|
|
|
|
|
|
forGenerate () {
|
2020-08-18 20:33:58 +00:00
|
|
|
this.nuxt.options.target = TARGETS.static
|
2020-07-02 13:02:35 +00:00
|
|
|
}
|
|
|
|
}
|