307 lines
		
	
	
		
			9.4 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			307 lines
		
	
	
		
			9.4 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| // config that are specific to --target app
 | |
| const fs = require('fs')
 | |
| const path = require('path')
 | |
| 
 | |
| // ensure the filename passed to html-webpack-plugin is a relative path
 | |
| // because it cannot correctly handle absolute paths
 | |
| function ensureRelative (outputDir, _path) {
 | |
|   if (path.isAbsolute(_path)) {
 | |
|     return path.relative(outputDir, _path)
 | |
|   } else {
 | |
|     return _path
 | |
|   }
 | |
| }
 | |
| 
 | |
| module.exports = (api, options) => {
 | |
|   api.chainWebpack(webpackConfig => {
 | |
|     // only apply when there's no alternative target
 | |
|     if (process.env.VUE_CLI_BUILD_TARGET && process.env.VUE_CLI_BUILD_TARGET !== 'app') {
 | |
|       return
 | |
|     }
 | |
| 
 | |
|     const isProd = process.env.NODE_ENV === 'production'
 | |
|     const isLegacyBundle = process.env.VUE_CLI_MODERN_MODE && !process.env.VUE_CLI_MODERN_BUILD
 | |
|     const outputDir = api.resolve(options.outputDir)
 | |
| 
 | |
|     const getAssetPath = require('../util/getAssetPath')
 | |
|     const outputFilename = getAssetPath(
 | |
|       options,
 | |
|       `js/[name]${isLegacyBundle ? `-legacy` : ``}${isProd && options.filenameHashing ? '.[contenthash:8]' : ''}.js`
 | |
|     )
 | |
|     webpackConfig
 | |
|       .output
 | |
|         .filename(outputFilename)
 | |
|         .chunkFilename(outputFilename)
 | |
| 
 | |
|     // code splitting
 | |
|     if (process.env.NODE_ENV !== 'test') {
 | |
|       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')
 | |
| 
 | |
|     // #1669 html-webpack-plugin's default sort uses toposort which cannot
 | |
|     // handle cyclic deps in certain cases. Monkey patch it to handle the case
 | |
|     // before we can upgrade to its 4.0 version (incompatible with preload atm)
 | |
|     const chunkSorters = require('html-webpack-plugin/lib/chunksorter')
 | |
|     const depSort = chunkSorters.dependency
 | |
|     chunkSorters.auto = chunkSorters.dependency = (chunks, ...args) => {
 | |
|       try {
 | |
|         return depSort(chunks, ...args)
 | |
|       } catch (e) {
 | |
|         // fallback to a manual sort if that happens...
 | |
|         return chunks.sort((a, b) => {
 | |
|           // make sure user entry is loaded last so user CSS can override
 | |
|           // vendor CSS
 | |
|           if (a.id === 'app') {
 | |
|             return 1
 | |
|           } else if (b.id === 'app') {
 | |
|             return -1
 | |
|           } else if (a.entry !== b.entry) {
 | |
|             return b.entry ? -1 : 1
 | |
|           }
 | |
|           return 0
 | |
|         })
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     const htmlOptions = {
 | |
|       title: api.service.pkg.name,
 | |
|       templateParameters: (compilation, assets, pluginOptions) => {
 | |
|         // enhance html-webpack-plugin's built in template params
 | |
|         let stats
 | |
|         return Object.assign({
 | |
|           // make stats lazy as it is expensive
 | |
|           get webpack () {
 | |
|             return stats || (stats = compilation.getStats().toJson())
 | |
|           },
 | |
|           compilation: compilation,
 | |
|           webpackConfig: compilation.options,
 | |
|           htmlWebpackPlugin: {
 | |
|             files: assets,
 | |
|             options: pluginOptions
 | |
|           }
 | |
|         }, resolveClientEnv(options, true /* raw */))
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     // handle indexPath
 | |
|     if (options.indexPath !== 'index.html') {
 | |
|       // why not set filename for html-webpack-plugin?
 | |
|       // 1. It cannot handle absolute paths
 | |
|       // 2. Relative paths causes incorrect SW manifest to be generated (#2007)
 | |
|       webpackConfig
 | |
|         .plugin('move-index')
 | |
|         .use(require('../webpack/MovePlugin'), [
 | |
|           path.resolve(outputDir, 'index.html'),
 | |
|           path.resolve(outputDir, options.indexPath)
 | |
|         ])
 | |
|     }
 | |
| 
 | |
|     if (isProd) {
 | |
|       Object.assign(htmlOptions, {
 | |
|         minify: {
 | |
|           removeComments: true,
 | |
|           collapseWhitespace: true,
 | |
|           collapseBooleanAttributes: true,
 | |
|           removeScriptTypeAttributes: true
 | |
|           // more options:
 | |
|           // https://github.com/kangax/html-minifier#options-quick-reference
 | |
|         }
 | |
|       })
 | |
| 
 | |
|       // keep chunk ids stable so async chunks have consistent hash (#1916)
 | |
|       webpackConfig
 | |
|         .plugin('named-chunks')
 | |
|           .use(require('webpack/lib/NamedChunksPlugin'), [chunk => {
 | |
|             if (chunk.name) {
 | |
|               return chunk.name
 | |
|             }
 | |
| 
 | |
|             const hash = require('hash-sum')
 | |
|             const joinedHash = hash(
 | |
|               Array.from(chunk.modulesIterable, m => m.id).join('_')
 | |
|             )
 | |
|             return `chunk-` + joinedHash
 | |
|           }])
 | |
|     }
 | |
| 
 | |
|     // resolve HTML file(s)
 | |
|     const HTMLPlugin = require('html-webpack-plugin')
 | |
|     const PreloadPlugin = require('@vue/preload-webpack-plugin')
 | |
|     const multiPageConfig = options.pages
 | |
|     const htmlPath = api.resolve('public/index.html')
 | |
|     const defaultHtmlPath = path.resolve(__dirname, 'index-default.html')
 | |
|     const publicCopyIgnore = ['.DS_Store']
 | |
| 
 | |
|     if (!multiPageConfig) {
 | |
|       // default, single page setup.
 | |
|       htmlOptions.template = fs.existsSync(htmlPath)
 | |
|         ? htmlPath
 | |
|         : defaultHtmlPath
 | |
| 
 | |
|       publicCopyIgnore.push({
 | |
|         glob: path.relative(api.resolve('public'), api.resolve(htmlOptions.template)),
 | |
|         matchBase: false
 | |
|       })
 | |
| 
 | |
|       webpackConfig
 | |
|         .plugin('html')
 | |
|           .use(HTMLPlugin, [htmlOptions])
 | |
| 
 | |
|       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'
 | |
|             }])
 | |
|       }
 | |
|     } else {
 | |
|       // multi-page setup
 | |
|       webpackConfig.entryPoints.clear()
 | |
| 
 | |
|       const pages = Object.keys(multiPageConfig)
 | |
|       const normalizePageConfig = c => typeof c === 'string' ? { entry: c } : c
 | |
| 
 | |
|       pages.forEach(name => {
 | |
|         const pageConfig = normalizePageConfig(multiPageConfig[name])
 | |
|         const {
 | |
|           entry,
 | |
|           template = `public/${name}.html`,
 | |
|           filename = `${name}.html`,
 | |
|           chunks = ['chunk-vendors', 'chunk-common', name]
 | |
|         } = pageConfig
 | |
| 
 | |
|         // Currently Cypress v3.1.0 comes with a very old version of Node,
 | |
|         // which does not support object rest syntax.
 | |
|         // (https://github.com/cypress-io/cypress/issues/2253)
 | |
|         // So here we have to extract the customHtmlOptions manually.
 | |
|         const customHtmlOptions = {}
 | |
|         for (const key in pageConfig) {
 | |
|           if (
 | |
|             !['entry', 'template', 'filename', 'chunks'].includes(key)
 | |
|           ) {
 | |
|             customHtmlOptions[key] = pageConfig[key]
 | |
|           }
 | |
|         }
 | |
| 
 | |
|         // inject entry
 | |
|         const entries = Array.isArray(entry) ? entry : [entry]
 | |
|         webpackConfig.entry(name).merge(entries.map(e => api.resolve(e)))
 | |
| 
 | |
|         // resolve page index template
 | |
|         const hasDedicatedTemplate = fs.existsSync(api.resolve(template))
 | |
|         const templatePath = hasDedicatedTemplate
 | |
|           ? template
 | |
|           : fs.existsSync(htmlPath)
 | |
|             ? htmlPath
 | |
|             : defaultHtmlPath
 | |
| 
 | |
|         publicCopyIgnore.push({
 | |
|           glob: path.relative(api.resolve('public'), api.resolve(templatePath)),
 | |
|           matchBase: false
 | |
|         })
 | |
| 
 | |
|         // inject html plugin for the page
 | |
|         const pageHtmlOptions = Object.assign(
 | |
|           {},
 | |
|           htmlOptions,
 | |
|           {
 | |
|             chunks,
 | |
|             template: templatePath,
 | |
|             filename: ensureRelative(outputDir, filename)
 | |
|           },
 | |
|           customHtmlOptions
 | |
|         )
 | |
| 
 | |
|         webpackConfig
 | |
|           .plugin(`html-${name}`)
 | |
|             .use(HTMLPlugin, [pageHtmlOptions])
 | |
|       })
 | |
| 
 | |
|       if (!isLegacyBundle) {
 | |
|         pages.forEach(name => {
 | |
|           const filename = ensureRelative(
 | |
|             outputDir,
 | |
|             normalizePageConfig(multiPageConfig[name]).filename || `${name}.html`
 | |
|           )
 | |
|           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]
 | |
|                 }
 | |
|               }])
 | |
|         })
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     // CORS and Subresource Integrity
 | |
|     if (options.crossorigin != null || options.integrity) {
 | |
|       webpackConfig
 | |
|         .plugin('cors')
 | |
|           .use(require('../webpack/CorsPlugin'), [{
 | |
|             crossorigin: options.crossorigin,
 | |
|             integrity: options.integrity,
 | |
|             publicPath: options.publicPath
 | |
|           }])
 | |
|     }
 | |
| 
 | |
|     // copy static assets in public/
 | |
|     const publicDir = api.resolve('public')
 | |
|     if (!isLegacyBundle && fs.existsSync(publicDir)) {
 | |
|       webpackConfig
 | |
|         .plugin('copy')
 | |
|           .use(require('copy-webpack-plugin'), [[{
 | |
|             from: publicDir,
 | |
|             to: outputDir,
 | |
|             toType: 'dir',
 | |
|             ignore: publicCopyIgnore
 | |
|           }]])
 | |
|     }
 | |
|   })
 | |
| }
 | 
