app.js 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. // config that are specific to --target app
  2. const fs = require('fs')
  3. const path = require('path')
  4. // ensure the filename passed to html-webpack-plugin is a relative path
  5. // because it cannot correctly handle absolute paths
  6. function ensureRelative (outputDir, _path) {
  7. if (path.isAbsolute(_path)) {
  8. return path.relative(outputDir, _path)
  9. } else {
  10. return _path
  11. }
  12. }
  13. module.exports = (api, options) => {
  14. api.chainWebpack(webpackConfig => {
  15. // only apply when there's no alternative target
  16. if (process.env.VUE_CLI_BUILD_TARGET && process.env.VUE_CLI_BUILD_TARGET !== 'app') {
  17. return
  18. }
  19. const isProd = process.env.NODE_ENV === 'production'
  20. const isLegacyBundle = process.env.VUE_CLI_MODERN_MODE && !process.env.VUE_CLI_MODERN_BUILD
  21. const outputDir = api.resolve(options.outputDir)
  22. const getAssetPath = require('../util/getAssetPath')
  23. const outputFilename = getAssetPath(
  24. options,
  25. `js/[name]${isLegacyBundle ? `-legacy` : ``}${isProd && options.filenameHashing ? '.[contenthash:8]' : ''}.js`
  26. )
  27. webpackConfig
  28. .output
  29. .filename(outputFilename)
  30. .chunkFilename(outputFilename)
  31. // FIXME: a temporary workaround to get accurate contenthash in `applyLegacy`
  32. // Should use a better fix per discussions at <https://github.com/jantimon/html-webpack-plugin/issues/1554#issuecomment-753653580>
  33. webpackConfig.optimization
  34. .set('realContentHash', false)
  35. // code splitting
  36. if (process.env.NODE_ENV !== 'test') {
  37. webpackConfig.optimization.splitChunks({
  38. cacheGroups: {
  39. defaultVendors: {
  40. name: `chunk-vendors`,
  41. test: /[\\/]node_modules[\\/]/,
  42. priority: -10,
  43. chunks: 'initial'
  44. },
  45. common: {
  46. name: `chunk-common`,
  47. minChunks: 2,
  48. priority: -20,
  49. chunks: 'initial',
  50. reuseExistingChunk: true
  51. }
  52. }
  53. })
  54. }
  55. // HTML plugin
  56. const resolveClientEnv = require('../util/resolveClientEnv')
  57. const htmlOptions = {
  58. title: api.service.pkg.name,
  59. scriptLoading: 'defer',
  60. templateParameters: (compilation, assets, assetTags, pluginOptions) => {
  61. // enhance html-webpack-plugin's built in template params
  62. return Object.assign({
  63. compilation: compilation,
  64. webpackConfig: compilation.options,
  65. htmlWebpackPlugin: {
  66. tags: assetTags,
  67. files: assets,
  68. options: pluginOptions
  69. }
  70. }, resolveClientEnv(options, true /* raw */))
  71. }
  72. }
  73. // handle indexPath
  74. if (options.indexPath !== 'index.html') {
  75. // why not set filename for html-webpack-plugin?
  76. // 1. It cannot handle absolute paths
  77. // 2. Relative paths causes incorrect SW manifest to be generated (#2007)
  78. webpackConfig
  79. .plugin('move-index')
  80. .use(require('../webpack/MovePlugin'), [
  81. path.resolve(outputDir, 'index.html'),
  82. path.resolve(outputDir, options.indexPath)
  83. ])
  84. }
  85. // resolve HTML file(s)
  86. const HTMLPlugin = require('html-webpack-plugin')
  87. // const PreloadPlugin = require('@vue/preload-webpack-plugin')
  88. const multiPageConfig = options.pages
  89. const htmlPath = api.resolve('public/index.html')
  90. const defaultHtmlPath = path.resolve(__dirname, 'index-default.html')
  91. const publicCopyIgnore = ['**/.DS_Store']
  92. if (!multiPageConfig) {
  93. // default, single page setup.
  94. htmlOptions.template = fs.existsSync(htmlPath)
  95. ? htmlPath
  96. : defaultHtmlPath
  97. publicCopyIgnore.push(api.resolve(htmlOptions.template).replace(/\\/g, '/'))
  98. webpackConfig
  99. .plugin('html')
  100. .use(HTMLPlugin, [htmlOptions])
  101. // FIXME: need to test out preload plugin's compatibility with html-webpack-plugin 4/5
  102. // if (!isLegacyBundle) {
  103. // // inject preload/prefetch to HTML
  104. // webpackConfig
  105. // .plugin('preload')
  106. // .use(PreloadPlugin, [{
  107. // rel: 'preload',
  108. // include: 'initial',
  109. // fileBlacklist: [/\.map$/, /hot-update\.js$/]
  110. // }])
  111. // webpackConfig
  112. // .plugin('prefetch')
  113. // .use(PreloadPlugin, [{
  114. // rel: 'prefetch',
  115. // include: 'asyncChunks'
  116. // }])
  117. // }
  118. } else {
  119. // multi-page setup
  120. webpackConfig.entryPoints.clear()
  121. const pages = Object.keys(multiPageConfig)
  122. const normalizePageConfig = c => typeof c === 'string' ? { entry: c } : c
  123. pages.forEach(name => {
  124. const pageConfig = normalizePageConfig(multiPageConfig[name])
  125. const {
  126. entry,
  127. template = `public/${name}.html`,
  128. filename = `${name}.html`,
  129. chunks = ['chunk-vendors', 'chunk-common', name]
  130. } = pageConfig
  131. // Currently Cypress v3.1.0 comes with a very old version of Node,
  132. // which does not support object rest syntax.
  133. // (https://github.com/cypress-io/cypress/issues/2253)
  134. // So here we have to extract the customHtmlOptions manually.
  135. const customHtmlOptions = {}
  136. for (const key in pageConfig) {
  137. if (
  138. !['entry', 'template', 'filename', 'chunks'].includes(key)
  139. ) {
  140. customHtmlOptions[key] = pageConfig[key]
  141. }
  142. }
  143. // inject entry
  144. const entries = Array.isArray(entry) ? entry : [entry]
  145. webpackConfig.entry(name).merge(entries.map(e => api.resolve(e)))
  146. // trim inline loader
  147. // * See https://github.com/jantimon/html-webpack-plugin/blob/master/docs/template-option.md#2-setting-a-loader-directly-for-the-template
  148. const templateWithoutLoader = template.replace(/^.+!/, '').replace(/\?.+$/, '')
  149. // resolve page index template
  150. const hasDedicatedTemplate = fs.existsSync(api.resolve(templateWithoutLoader))
  151. const templatePath = hasDedicatedTemplate
  152. ? template
  153. : fs.existsSync(htmlPath)
  154. ? htmlPath
  155. : defaultHtmlPath
  156. publicCopyIgnore.push(api.resolve(templateWithoutLoader).replace(/\\/g, '/'))
  157. // inject html plugin for the page
  158. const pageHtmlOptions = Object.assign(
  159. {},
  160. htmlOptions,
  161. {
  162. chunks,
  163. template: templatePath,
  164. filename: ensureRelative(outputDir, filename)
  165. },
  166. customHtmlOptions
  167. )
  168. webpackConfig
  169. .plugin(`html-${name}`)
  170. .use(HTMLPlugin, [pageHtmlOptions])
  171. })
  172. // FIXME: preload plugin is not compatible with webpack 5 / html-webpack-plugin 4 yet
  173. // if (!isLegacyBundle) {
  174. // pages.forEach(name => {
  175. // const filename = ensureRelative(
  176. // outputDir,
  177. // normalizePageConfig(multiPageConfig[name]).filename || `${name}.html`
  178. // )
  179. // webpackConfig
  180. // .plugin(`preload-${name}`)
  181. // .use(PreloadPlugin, [{
  182. // rel: 'preload',
  183. // includeHtmlNames: [filename],
  184. // include: {
  185. // type: 'initial',
  186. // entries: [name]
  187. // },
  188. // fileBlacklist: [/\.map$/, /hot-update\.js$/]
  189. // }])
  190. // webpackConfig
  191. // .plugin(`prefetch-${name}`)
  192. // .use(PreloadPlugin, [{
  193. // rel: 'prefetch',
  194. // includeHtmlNames: [filename],
  195. // include: {
  196. // type: 'asyncChunks',
  197. // entries: [name]
  198. // }
  199. // }])
  200. // })
  201. // }
  202. }
  203. // CORS and Subresource Integrity
  204. if (options.crossorigin != null || options.integrity) {
  205. webpackConfig
  206. .plugin('cors')
  207. .use(require('../webpack/CorsPlugin'), [{
  208. crossorigin: options.crossorigin,
  209. integrity: options.integrity,
  210. publicPath: options.publicPath
  211. }])
  212. }
  213. // copy static assets in public/
  214. const publicDir = api.resolve('public')
  215. const CopyWebpackPlugin = require('copy-webpack-plugin')
  216. const PlaceholderPlugin = class PlaceholderPlugin { apply () {} }
  217. const copyOptions = {
  218. patterns: [{
  219. from: publicDir,
  220. to: outputDir,
  221. toType: 'dir',
  222. noErrorOnMissing: true,
  223. globOptions: {
  224. ignore: publicCopyIgnore
  225. },
  226. info: {
  227. minimized: true
  228. }
  229. }]
  230. }
  231. if (fs.existsSync(publicDir)) {
  232. if (isLegacyBundle) {
  233. webpackConfig.plugin('copy').use(PlaceholderPlugin, [copyOptions])
  234. } else {
  235. webpackConfig.plugin('copy').use(CopyWebpackPlugin, [copyOptions])
  236. }
  237. }
  238. })
  239. }