mirror of
https://github.com/vuejs/vue-cli.git
synced 2026-05-06 03:49:13 -05:00
feat: modern mode
This commit is contained in:
@@ -50,7 +50,9 @@ module.exports = (context, options = {}) => {
|
||||
|
||||
const targets = process.env.VUE_CLI_BABEL_TARGET_NODE
|
||||
? { node: 'current' }
|
||||
: rawTargets
|
||||
: process.env.VUE_CLI_MODERN_BUILD
|
||||
? { esmodules: true }
|
||||
: rawTargets
|
||||
|
||||
// included-by-default polyfills. These are common polyfills that 3rd party
|
||||
// dependencies may rely on (e.g. Vuex relies on Promise), but since with
|
||||
@@ -58,7 +60,12 @@ module.exports = (context, options = {}) => {
|
||||
// be force-included.
|
||||
let polyfills
|
||||
const buildTarget = process.env.VUE_CLI_TARGET || 'app'
|
||||
if (buildTarget === 'app' && useBuiltIns === 'usage' && !process.env.VUE_CLI_BABEL_TARGET_NODE) {
|
||||
if (
|
||||
buildTarget === 'app' &&
|
||||
useBuiltIns === 'usage' &&
|
||||
!process.env.VUE_CLI_BABEL_TARGET_NODE &&
|
||||
!process.env.VUE_CLI_MODERN_BUILD
|
||||
) {
|
||||
polyfills = getPolyfills(targets, userPolyfills || defaultPolyfills, {
|
||||
ignoreBrowserslistConfig,
|
||||
configPath
|
||||
|
||||
@@ -29,7 +29,8 @@ module.exports = (api, options) => {
|
||||
.options(api.genCacheConfig('babel-loader', {
|
||||
'@babel/core': require('@babel/core/package.json').version,
|
||||
'@vue/babel-preset-app': require('@vue/babel-preset-app').version,
|
||||
'babel-loader': require('babel-loader/package.json').version
|
||||
'babel-loader': require('babel-loader/package.json').version,
|
||||
modern: !!process.env.VUE_CLI_MODERN_BUILD
|
||||
}, 'babel.config.js'))
|
||||
.end()
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ module.exports = (api, options) => {
|
||||
'--no-clean': `do not remove the dist directory before building the project`,
|
||||
'--watch': `watch for changes`
|
||||
}
|
||||
}, async function build (args) {
|
||||
}, async (args) => {
|
||||
for (const key in defaults) {
|
||||
if (args[key] == null) {
|
||||
args[key] = defaults[key]
|
||||
@@ -32,161 +32,168 @@ module.exports = (api, options) => {
|
||||
args.entry = args.entry || 'src/App.vue'
|
||||
}
|
||||
|
||||
const fs = require('fs-extra')
|
||||
const path = require('path')
|
||||
const chalk = require('chalk')
|
||||
const webpack = require('webpack')
|
||||
const formatStats = require('./formatStats')
|
||||
const {
|
||||
log,
|
||||
done,
|
||||
info,
|
||||
logWithSpinner,
|
||||
stopSpinner
|
||||
} = require('@vue/cli-shared-utils')
|
||||
if (options.modernMode && args.target === 'app') {
|
||||
delete process.env.VUE_CLI_MODERN_BUILD
|
||||
await build(Object.assign({}, args, {
|
||||
modern: false
|
||||
}), api, options)
|
||||
|
||||
log()
|
||||
const mode = api.service.mode
|
||||
if (args.target === 'app') {
|
||||
logWithSpinner(`Building for ${mode}...`)
|
||||
process.env.VUE_CLI_MODERN_BUILD = true
|
||||
await build(Object.assign({}, args, {
|
||||
modern: true,
|
||||
clean: false
|
||||
}), api, options)
|
||||
} else {
|
||||
const buildMode = buildModes[args.target]
|
||||
if (buildMode) {
|
||||
logWithSpinner(`Building for ${mode} as ${buildMode}...`)
|
||||
} else {
|
||||
throw new Error(`Unknown build target: ${args.target}`)
|
||||
}
|
||||
return build(args, api, options)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const targetDir = api.resolve(args.dest || options.outputDir)
|
||||
async function build (args, api, options) {
|
||||
const fs = require('fs-extra')
|
||||
const path = require('path')
|
||||
const chalk = require('chalk')
|
||||
const webpack = require('webpack')
|
||||
const formatStats = require('./formatStats')
|
||||
const {
|
||||
log,
|
||||
done,
|
||||
info,
|
||||
logWithSpinner,
|
||||
stopSpinner
|
||||
} = require('@vue/cli-shared-utils')
|
||||
|
||||
// respect inline build destination in copy plugin
|
||||
if (args.dest) {
|
||||
api.chainWebpack(config => {
|
||||
if (config.plugins.has('copy')) {
|
||||
config.plugin('copy').tap(args => {
|
||||
args[0][0].to = targetDir
|
||||
return args
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// resolve raw webpack config
|
||||
process.env.VUE_CLI_BUILD_TARGET = args.target
|
||||
let webpackConfig
|
||||
if (args.target === 'lib') {
|
||||
webpackConfig = require('./resolveLibConfig')(api, args, options)
|
||||
} else if (
|
||||
args.target === 'wc' ||
|
||||
args.target === 'wc-async'
|
||||
) {
|
||||
webpackConfig = require('./resolveWcConfig')(api, args, options)
|
||||
log()
|
||||
const mode = api.service.mode
|
||||
if (args.target === 'app') {
|
||||
const bundleTag = options.modernMode
|
||||
? args.modern
|
||||
? `modern bundle `
|
||||
: `legacy bundle `
|
||||
: ``
|
||||
logWithSpinner(`Building ${bundleTag}for ${mode}...`)
|
||||
} else {
|
||||
const buildMode = buildModes[args.target]
|
||||
if (buildMode) {
|
||||
logWithSpinner(`Building for ${mode} as ${buildMode}...`)
|
||||
} else {
|
||||
webpackConfig = api.resolveWebpackConfig()
|
||||
if (args.entry && !options.pages) {
|
||||
webpackConfig.entry = { app: api.resolve(args.entry) }
|
||||
throw new Error(`Unknown build target: ${args.target}`)
|
||||
}
|
||||
}
|
||||
|
||||
const targetDir = api.resolve(args.dest || options.outputDir)
|
||||
|
||||
// resolve raw webpack config
|
||||
process.env.VUE_CLI_BUILD_TARGET = args.target
|
||||
let webpackConfig
|
||||
if (args.target === 'lib') {
|
||||
webpackConfig = require('./resolveLibConfig')(api, args, options)
|
||||
} else if (
|
||||
args.target === 'wc' ||
|
||||
args.target === 'wc-async'
|
||||
) {
|
||||
webpackConfig = require('./resolveWcConfig')(api, args, options)
|
||||
} else {
|
||||
webpackConfig = require('./resolveAppConfig')(api, args, options)
|
||||
}
|
||||
|
||||
// apply inline dest path after user configureWebpack hooks
|
||||
// so it takes higher priority
|
||||
if (args.dest) {
|
||||
const applyDest = config => {
|
||||
config.output.path = targetDir
|
||||
}
|
||||
if (Array.isArray(webpackConfig)) {
|
||||
webpackConfig.forEach(applyDest)
|
||||
} else {
|
||||
applyDest(webpackConfig)
|
||||
}
|
||||
}
|
||||
|
||||
// grab the actual output path and check for common mis-configuration
|
||||
const actualTargetDir = (
|
||||
Array.isArray(webpackConfig)
|
||||
? webpackConfig[0]
|
||||
: webpackConfig
|
||||
).output.path
|
||||
|
||||
if (args.watch) {
|
||||
webpackConfig.watch = true
|
||||
}
|
||||
|
||||
if (!args.dest && actualTargetDir !== api.resolve(options.outputDir)) {
|
||||
// user directly modifies output.path in configureWebpack or chainWebpack.
|
||||
// this is not supported because there's no way for us to give copy
|
||||
// plugin the correct value this way.
|
||||
console.error(chalk.red(
|
||||
`\n\nConfiguration Error: ` +
|
||||
`Avoid modifying webpack output.path directly. ` +
|
||||
`Use the "outputDir" option instead.\n`
|
||||
))
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
if (actualTargetDir === api.service.context) {
|
||||
console.error(chalk.red(
|
||||
`\n\nConfiguration Error: ` +
|
||||
`Do not set output directory to project root.\n`
|
||||
))
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
if (args.clean) {
|
||||
await fs.remove(targetDir)
|
||||
}
|
||||
|
||||
// Expose advanced stats
|
||||
if (args.dashboard) {
|
||||
const DashboardPlugin = require('../../webpack/DashboardPlugin')
|
||||
;(webpackConfig.plugins = webpackConfig.plugins || []).push(new DashboardPlugin({
|
||||
type: 'build'
|
||||
}))
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
webpack(webpackConfig, (err, stats) => {
|
||||
stopSpinner(false)
|
||||
if (err) {
|
||||
return reject(err)
|
||||
}
|
||||
}
|
||||
|
||||
// apply inline dest path after user configureWebpack hooks
|
||||
// so it takes higher priority
|
||||
if (args.dest) {
|
||||
const applyDest = config => {
|
||||
config.output.path = targetDir
|
||||
if (stats.hasErrors()) {
|
||||
return reject(`Build failed with errors.`)
|
||||
}
|
||||
if (Array.isArray(webpackConfig)) {
|
||||
webpackConfig.forEach(applyDest)
|
||||
} else {
|
||||
applyDest(webpackConfig)
|
||||
}
|
||||
}
|
||||
|
||||
// grab the actual output path and check for common mis-configuration
|
||||
const actualTargetDir = (
|
||||
Array.isArray(webpackConfig)
|
||||
? webpackConfig[0]
|
||||
: webpackConfig
|
||||
).output.path
|
||||
|
||||
if (args.watch) {
|
||||
webpackConfig.watch = true
|
||||
}
|
||||
|
||||
if (!args.dest && actualTargetDir !== api.resolve(options.outputDir)) {
|
||||
// user directly modifies output.path in configureWebpack or chainWebpack.
|
||||
// this is not supported because there's no way for us to give copy
|
||||
// plugin the correct value this way.
|
||||
console.error(chalk.red(
|
||||
`\n\nConfiguration Error: ` +
|
||||
`Avoid modifying webpack output.path directly. ` +
|
||||
`Use the "outputDir" option instead.\n`
|
||||
))
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
if (actualTargetDir === api.service.context) {
|
||||
console.error(chalk.red(
|
||||
`\n\nConfiguration Error: ` +
|
||||
`Do not set output directory to project root.\n`
|
||||
))
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
if (args.clean) {
|
||||
await fs.remove(targetDir)
|
||||
}
|
||||
|
||||
// Expose advanced stats
|
||||
if (args.dashboard) {
|
||||
const DashboardPlugin = require('../../webpack/DashboardPlugin')
|
||||
;(webpackConfig.plugins = webpackConfig.plugins || []).push(new DashboardPlugin({
|
||||
type: 'build'
|
||||
}))
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
webpack(webpackConfig, (err, stats) => {
|
||||
stopSpinner(false)
|
||||
if (err) {
|
||||
return reject(err)
|
||||
}
|
||||
|
||||
if (stats.hasErrors()) {
|
||||
return reject(`Build failed with errors.`)
|
||||
}
|
||||
|
||||
if (!args.silent) {
|
||||
const targetDirShort = path.relative(
|
||||
api.service.context,
|
||||
targetDir
|
||||
)
|
||||
log(formatStats(stats, targetDirShort, api))
|
||||
if (args.target === 'app') {
|
||||
if (!args.watch) {
|
||||
done(`Build complete. The ${chalk.cyan(targetDirShort)} directory is ready to be deployed.\n`)
|
||||
} else {
|
||||
done(`Build complete. Watching for changes...`)
|
||||
}
|
||||
if (
|
||||
options.baseUrl === '/' &&
|
||||
// only log the tips if this is the first build
|
||||
!fs.existsSync(api.resolve('node_modules/.cache'))
|
||||
) {
|
||||
info(`The app is built assuming that it will be deployed at the root of a domain.`)
|
||||
info(`If you intend to deploy it under a subpath, update the ${chalk.green('baseUrl')} option`)
|
||||
info(`in your project config (${chalk.cyan(`vue.config.js`)} or ${chalk.green('"vue"')} field in ${chalk.cyan(`package.json`)}).\n`)
|
||||
}
|
||||
if (!args.silent) {
|
||||
const targetDirShort = path.relative(
|
||||
api.service.context,
|
||||
targetDir
|
||||
)
|
||||
log(formatStats(stats, targetDirShort, api))
|
||||
if (args.target === 'app' && !(options.modernMode && !args.modern)) {
|
||||
if (!args.watch) {
|
||||
done(`Build complete. The ${chalk.cyan(targetDirShort)} directory is ready to be deployed.\n`)
|
||||
} else {
|
||||
done(`Build complete. Watching for changes...`)
|
||||
}
|
||||
if (
|
||||
options.baseUrl === '/' &&
|
||||
// only log the tips if this is the first build
|
||||
!fs.existsSync(api.resolve('node_modules/.cache'))
|
||||
) {
|
||||
info(`The app is built assuming that it will be deployed at the root of a domain.`)
|
||||
info(`If you intend to deploy it under a subpath, update the ${chalk.green('baseUrl')} option`)
|
||||
info(`in your project config (${chalk.cyan(`vue.config.js`)} or ${chalk.green('"vue"')} field in ${chalk.cyan(`package.json`)}).\n`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// test-only signal
|
||||
if (process.env.VUE_CLI_TEST) {
|
||||
console.log('Build complete.')
|
||||
}
|
||||
// test-only signal
|
||||
if (process.env.VUE_CLI_TEST) {
|
||||
console.log('Build complete.')
|
||||
}
|
||||
|
||||
resolve()
|
||||
})
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
module.exports = (api, args, options) => {
|
||||
const config = api.resolveChainableWebpackConfig()
|
||||
const targetDir = api.resolve(args.dest || options.outputDir)
|
||||
|
||||
// respect inline build destination in copy plugin
|
||||
if (args.dest && config.plugins.has('copy')) {
|
||||
config.plugin('copy').tap(args => {
|
||||
args[0][0].to = targetDir
|
||||
return args
|
||||
})
|
||||
}
|
||||
|
||||
if (options.modernMode) {
|
||||
const ModernModePlugin = require('../../webpack/ModernModePlugin')
|
||||
const isModernBuild = !!process.env.VUE_CLI_MODERN_BUILD
|
||||
if (!isModernBuild) {
|
||||
// Inject plugin to extract build stats and write to disk
|
||||
config
|
||||
.plugin('modern-mode-legacy')
|
||||
.use(ModernModePlugin, [targetDir, false])
|
||||
} else {
|
||||
// Inject plugin to read non-modern build stats and inject HTML
|
||||
config
|
||||
.plugin('modern-mode-modern')
|
||||
.use(ModernModePlugin, [targetDir, true])
|
||||
}
|
||||
}
|
||||
|
||||
const rawConfig = config.toConfig()
|
||||
|
||||
// respect inline entry
|
||||
if (args.entry && !options.pages) {
|
||||
rawConfig.entry = { app: api.resolve(args.entry) }
|
||||
}
|
||||
|
||||
return rawConfig
|
||||
}
|
||||
@@ -10,6 +10,29 @@ module.exports = (api, options) => {
|
||||
}
|
||||
|
||||
const isProd = process.env.NODE_ENV === 'production'
|
||||
const isLegacyBundle = options.modernMode && !process.env.VUE_CLI_MODERN_BUILD
|
||||
|
||||
// code splitting
|
||||
if (isProd) {
|
||||
webpackConfig
|
||||
.optimization.splitChunks({
|
||||
cacheGroups: {
|
||||
vendors: {
|
||||
name: `chunk-vendors`,
|
||||
test: /[\\/]node_modules[\\/]/,
|
||||
priority: -10,
|
||||
chunks: 'initial'
|
||||
},
|
||||
common: {
|
||||
name: `chunk-common`,
|
||||
minChunks: 2,
|
||||
priority: -20,
|
||||
chunks: 'initial',
|
||||
reuseExistingChunk: true
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// HTML plugin
|
||||
const resolveClientEnv = require('../util/resolveClientEnv')
|
||||
@@ -38,7 +61,9 @@ module.exports = (api, options) => {
|
||||
minify: {
|
||||
removeComments: true,
|
||||
collapseWhitespace: true,
|
||||
removeAttributeQuotes: true
|
||||
removeAttributeQuotes: true,
|
||||
collapseBooleanAttributes: true,
|
||||
removeScriptTypeAttributes: true
|
||||
// more options:
|
||||
// https://github.com/kangax/html-minifier#options-quick-reference
|
||||
},
|
||||
@@ -64,21 +89,23 @@ module.exports = (api, options) => {
|
||||
.plugin('html')
|
||||
.use(HTMLPlugin, [htmlOptions])
|
||||
|
||||
// inject preload/prefetch to HTML
|
||||
webpackConfig
|
||||
.plugin('preload')
|
||||
.use(PreloadPlugin, [{
|
||||
rel: 'preload',
|
||||
include: 'initial',
|
||||
fileBlacklist: [/\.map$/, /hot-update\.js$/]
|
||||
}])
|
||||
if (!isLegacyBundle) {
|
||||
// inject preload/prefetch to HTML
|
||||
webpackConfig
|
||||
.plugin('preload')
|
||||
.use(PreloadPlugin, [{
|
||||
rel: 'preload',
|
||||
include: 'initial',
|
||||
fileBlacklist: [/\.map$/, /hot-update\.js$/]
|
||||
}])
|
||||
|
||||
webpackConfig
|
||||
.plugin('prefetch')
|
||||
.use(PreloadPlugin, [{
|
||||
rel: 'prefetch',
|
||||
include: 'asyncChunks'
|
||||
}])
|
||||
webpackConfig
|
||||
.plugin('prefetch')
|
||||
.use(PreloadPlugin, [{
|
||||
rel: 'prefetch',
|
||||
include: 'asyncChunks'
|
||||
}])
|
||||
}
|
||||
} else {
|
||||
// multi-page setup
|
||||
webpackConfig.entryPoints.clear()
|
||||
@@ -107,37 +134,39 @@ module.exports = (api, options) => {
|
||||
.use(HTMLPlugin, [pageHtmlOptions])
|
||||
})
|
||||
|
||||
pages.forEach(name => {
|
||||
const {
|
||||
filename = `${name}.html`
|
||||
} = normalizePageConfig(multiPageConfig[name])
|
||||
webpackConfig
|
||||
.plugin(`preload-${name}`)
|
||||
.use(PreloadPlugin, [{
|
||||
rel: 'preload',
|
||||
includeHtmlNames: [filename],
|
||||
include: {
|
||||
type: 'initial',
|
||||
entries: [name]
|
||||
},
|
||||
fileBlacklist: [/\.map$/, /hot-update\.js$/]
|
||||
}])
|
||||
if (!isLegacyBundle) {
|
||||
pages.forEach(name => {
|
||||
const {
|
||||
filename = `${name}.html`
|
||||
} = normalizePageConfig(multiPageConfig[name])
|
||||
webpackConfig
|
||||
.plugin(`preload-${name}`)
|
||||
.use(PreloadPlugin, [{
|
||||
rel: 'preload',
|
||||
includeHtmlNames: [filename],
|
||||
include: {
|
||||
type: 'initial',
|
||||
entries: [name]
|
||||
},
|
||||
fileBlacklist: [/\.map$/, /hot-update\.js$/]
|
||||
}])
|
||||
|
||||
webpackConfig
|
||||
.plugin(`prefetch-${name}`)
|
||||
.use(PreloadPlugin, [{
|
||||
rel: 'prefetch',
|
||||
includeHtmlNames: [filename],
|
||||
include: {
|
||||
type: 'asyncChunks',
|
||||
entries: [name]
|
||||
}
|
||||
}])
|
||||
})
|
||||
webpackConfig
|
||||
.plugin(`prefetch-${name}`)
|
||||
.use(PreloadPlugin, [{
|
||||
rel: 'prefetch',
|
||||
includeHtmlNames: [filename],
|
||||
include: {
|
||||
type: 'asyncChunks',
|
||||
entries: [name]
|
||||
}
|
||||
}])
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// copy static assets in public/
|
||||
if (fs.existsSync(api.resolve('public'))) {
|
||||
if (!isLegacyBundle && fs.existsSync(api.resolve('public'))) {
|
||||
webpackConfig
|
||||
.plugin('copy')
|
||||
.use(require('copy-webpack-plugin'), [[{
|
||||
@@ -146,27 +175,5 @@ module.exports = (api, options) => {
|
||||
ignore: ['index.html', '.DS_Store']
|
||||
}]])
|
||||
}
|
||||
|
||||
// code splitting
|
||||
if (isProd) {
|
||||
webpackConfig
|
||||
.optimization.splitChunks({
|
||||
cacheGroups: {
|
||||
vendors: {
|
||||
name: 'chunk-vendors',
|
||||
test: /[\\/]node_modules[\\/]/,
|
||||
priority: -10,
|
||||
chunks: 'initial'
|
||||
},
|
||||
common: {
|
||||
name: 'chunk-common',
|
||||
minChunks: 2,
|
||||
priority: -20,
|
||||
chunks: 'initial',
|
||||
reuseExistingChunk: true
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
module.exports = (api, options) => {
|
||||
api.chainWebpack(webpackConfig => {
|
||||
const isLegacyBundle = options.modernMode && !process.env.VUE_CLI_MODERN_BUILD
|
||||
const resolveLocal = require('../util/resolveLocal')
|
||||
const getAssetPath = require('../util/getAssetPath')
|
||||
const inlineLimit = 10000
|
||||
@@ -12,7 +13,7 @@ module.exports = (api, options) => {
|
||||
.end()
|
||||
.output
|
||||
.path(api.resolve(options.outputDir))
|
||||
.filename('[name].js')
|
||||
.filename(isLegacyBundle ? '[name]-legacy.js' : '[name].js')
|
||||
.publicPath(options.baseUrl)
|
||||
|
||||
webpackConfig.resolve
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
module.exports = (api, options) => {
|
||||
api.chainWebpack(webpackConfig => {
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
const isLegacyBundle = options.modernMode && !process.env.VUE_CLI_MODERN_BUILD
|
||||
const getAssetPath = require('../util/getAssetPath')
|
||||
const filename = getAssetPath(
|
||||
options,
|
||||
`js/[name].[chunkhash:8].js`,
|
||||
`js/[name]${isLegacyBundle ? `-legacy` : ``}.[chunkhash:8].js`,
|
||||
true /* placeAtRootIfRelative */
|
||||
)
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ const schema = createSchema(joi => joi.object({
|
||||
baseUrl: joi.string().allow(''),
|
||||
outputDir: joi.string(),
|
||||
assetsDir: joi.string(),
|
||||
modernMode: joi.boolean(),
|
||||
runtimeCompiler: joi.boolean(),
|
||||
transpileDependencies: joi.array(),
|
||||
productionSourceMap: joi.boolean(),
|
||||
@@ -54,6 +55,9 @@ exports.defaults = () => ({
|
||||
// where to put static assets (js/css/img/font/...)
|
||||
assetsDir: '',
|
||||
|
||||
// ship minimally-transpiled ES2015 along with a legacy bundle
|
||||
modernMode: false,
|
||||
|
||||
// boolean, use full build?
|
||||
runtimeCompiler: false,
|
||||
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
const fs = require('fs-extra')
|
||||
const path = require('path')
|
||||
|
||||
// https://gist.github.com/samthor/64b114e4a4f539915a95b91ffd340acc
|
||||
const Safari10NoModuleFix = `!function(){var e=document,t=e.createElement("script");if(!("noModule"in t)&&"onbeforeload"in t){var n=!1;e.addEventListener("beforeload",function(e){if(e.target===t)n=!0;else if(!e.target.hasAttribute("nomodule")||!n)return;e.preventDefault()},!0),t.type="module",t.src=".",e.head.appendChild(t),t.remove()}}();`
|
||||
|
||||
class ModernModePlugin {
|
||||
constructor (targetDir, isModern) {
|
||||
this.targetDir = targetDir
|
||||
this.isModern = isModern
|
||||
}
|
||||
|
||||
apply (compiler) {
|
||||
if (!this.isModern) {
|
||||
this.applyLegacy(compiler)
|
||||
} else {
|
||||
this.applyModern(compiler)
|
||||
}
|
||||
}
|
||||
|
||||
applyLegacy (compiler) {
|
||||
const ID = `vue-cli-legacy-bundle`
|
||||
compiler.hooks.compilation.tap(ID, compilation => {
|
||||
compilation.hooks.htmlWebpackPluginAlterAssetTags.tapAsync(ID, async (data, cb) => {
|
||||
// get stats, write to disk
|
||||
await fs.ensureDir(this.targetDir)
|
||||
const htmlName = data.plugin.options.filename
|
||||
const tempFilename = path.join(this.targetDir, `legacy-assets-${htmlName}.json`)
|
||||
await fs.writeFile(tempFilename, JSON.stringify(data.body))
|
||||
cb()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
applyModern (compiler) {
|
||||
const ID = `vue-cli-modern-bundle`
|
||||
compiler.hooks.compilation.tap(ID, compilation => {
|
||||
compilation.hooks.htmlWebpackPluginAlterAssetTags.tapAsync(ID, async (data, cb) => {
|
||||
// use <script type="module"> for modern assets
|
||||
const modernAssets = data.body.filter(a => a.tagName === 'script')
|
||||
modernAssets.forEach(a => { a.attributes.type = 'module' })
|
||||
|
||||
// inject Safari 10 nomdoule fix
|
||||
data.body.push({
|
||||
tagName: 'script',
|
||||
closeTag: true,
|
||||
innerHTML: Safari10NoModuleFix
|
||||
})
|
||||
|
||||
// inject links for legacy assets as <script nomodule>
|
||||
const htmlName = data.plugin.options.filename
|
||||
const tempFilename = path.join(this.targetDir, `legacy-assets-${htmlName}.json`)
|
||||
const legacyAssets = JSON.parse(await fs.readFile(tempFilename, 'utf-8'))
|
||||
.filter(a => a.tagName === 'script')
|
||||
legacyAssets.forEach(a => { a.attributes.nomodule = '' })
|
||||
data.body.push(...legacyAssets)
|
||||
await fs.remove(tempFilename)
|
||||
cb()
|
||||
})
|
||||
|
||||
compilation.hooks.htmlWebpackPluginAfterHtmlProcessing.tap(ID, data => {
|
||||
data.html = data.html
|
||||
// use <link rel="modulepreload"> instead of <link rel="preload">
|
||||
// for modern assets
|
||||
.replace(/(<link as=script .*?)rel=preload>/g, '$1rel=modulepreload>')
|
||||
.replace(/\snomodule="">/g, ' nomodule>')
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ModernModePlugin
|
||||
Reference in New Issue
Block a user