vue.config.js 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. const path = require('path')
  2. const { SourceMapDevToolPlugin, DefinePlugin } = require('webpack')
  3. function resolve (dir) {
  4. return path.join(__dirname, dir)
  5. }
  6. const { getPages, changeProjectContext, getDirectories, checkProjectConfig, combineStyle } = require('./webpackUtils')
  7. const existsPages = getDirectories('./public')
  8. // 获取多页面文件夹
  9. const pages = getPages()
  10. // 校验新建页面是否与已有页面重复
  11. const pageNameDuplicated = existsPages.filter(name => pages[name])
  12. if (pageNameDuplicated.length > 0) {
  13. throw Error(`名为${pageNameDuplicated.join('、')}的页面已经存在,不能重复创建`)
  14. }
  15. const pageNames = Object.keys(pages)
  16. // 指定平台生成文件夹
  17. const platformPages = existsPages.filter(name => name.indexOf('-') === -1)
  18. // 开发服务器需要的配置
  19. const config = require('./public/common-assets/js/project.config')
  20. const { version: projectVersion, projectContext, backServerRoot, backServerType, proxyPath, assetsContext, loginPath, staticDir = 'static' } = config
  21. // 配置校验
  22. checkProjectConfig(config)
  23. // 获取单体后端上下文,供兼容JSP页面请求使用
  24. let soaBackServerContext = '__msaNeverIn__'
  25. if (backServerType === 'soa') {
  26. let root = backServerRoot.endsWith('/') ? backServerRoot.substring(0, backServerRoot.length - 1) : backServerRoot
  27. soaBackServerContext = root.substring(root.lastIndexOf('/'), root.length)
  28. }
  29. // 输出路径
  30. const outputDir = `dist${projectContext}/${projectVersion}${projectContext}`
  31. // 工程可以指定自己的目录
  32. const assetsDir = process.env.NODE_ENV === 'production' ? staticDir : ''
  33. module.exports = {
  34. // 多页面配置
  35. pages,
  36. // 工程上下文
  37. publicPath: projectContext,
  38. // 编译输出目录
  39. outputDir,
  40. // 静态文件父级目录
  41. assetsDir,
  42. // 默认在生成的静态资源文件名中包含hash以控制缓存
  43. filenameHashing: true,
  44. // 是否使用包含运行时编译器的 Vue 构建版本
  45. runtimeCompiler: false,
  46. // 如果这个值是一个对象,则会通过 webpack-merge 合并到最终的配置中
  47. // 如果你需要基于环境有条件地配置行为,或者想要直接修改配置,那就换成一个函数 (该函数会在环境变量被设置之后懒执行)。该方法的第一个参数会收到已经解析好的配置。在函数内,你可以直接修改配置,或者返回一个将会被合并的对象
  48. configureWebpack () {
  49. let config = {
  50. output: {},
  51. plugins: [],
  52. }
  53. // 运行时配置
  54. if (process.env.NODE_ENV === 'production') {
  55. // 输出主文件放置在对应页面目录
  56. config.output.filename = '[name]/[name].[contenthash].js'
  57. // 关闭默认配置,使用插件添加source map
  58. config.devtool = false
  59. const timestamp = (new Date()).toISOString().replace(/\D/g, '')
  60. config.plugins.push(
  61. new SourceMapDevToolPlugin({
  62. // 暂不将引用加到文件中
  63. append: false,
  64. // 指定sourceMap的生成路径
  65. filename: `${assetsDir}/source-map/${timestamp}/[file].map`,
  66. // 公共服务地址
  67. publicPath: 'http://127.0.0.1/',
  68. fileContext: '',
  69. }),
  70. )
  71. }
  72. config.plugins.push(
  73. new DefinePlugin({
  74. ASSETS_CONTEXT: `'${assetsContext}'`,
  75. DIREWOLF_VERSION: `'${process.env.npm_package_version}'`,
  76. }),
  77. {
  78. apply (compiler) {
  79. // 运行时插件,编译前判断工程上下文是否正确
  80. compiler.hooks.beforeCompile.tap('changeProjectContext', () => changeProjectContext(projectContext))
  81. },
  82. }, {
  83. apply (compiler) {
  84. // 运行时插件,编译完成后创建库文件
  85. if (process.env.NODE_ENV === 'production') {
  86. compiler.hooks.done.tap('combineCss', () => combineStyle(outputDir, assetsDir, pageNames))
  87. }
  88. },
  89. })
  90. config.optimization = {
  91. splitChunks: {
  92. cacheGroups: {
  93. 'async-commons': {
  94. chunks: 'async',
  95. minChunks: 2,
  96. name: 'async-commons',
  97. priority: 80,
  98. },
  99. },
  100. },
  101. }
  102. config.externals = [{
  103. 'vue': 'Vue',
  104. 'element-ui': 'ELEMENT',
  105. 'vue-router': 'VueRouter',
  106. 'vuex': 'Vuex',
  107. 'axios': 'axios',
  108. 'clipboard': 'ClipboardJS',
  109. 'vue-echarts': 'VueECharts',
  110. 'vue-slimscroll': 'VueSlimScroll',
  111. 'vue-grid-layout': 'VueGridLayout',
  112. 'qs': 'Qs',
  113. }, /\/(echarts)\//]
  114. return config
  115. },
  116. // 对内部的 webpack 配置(比如修改、增加Loader选项)(链式操作)
  117. chainWebpack (config) {
  118. // 目录别名
  119. config.resolve.alias.set('@', resolve('src/pages'))
  120. .set('@assets', resolve('src/assets'))
  121. /**
  122. * 删除懒加载模块的prefetch,降低带宽压力
  123. * https://cli.vuejs.org/zh/guide/html-and-static-assets.html#prefetch
  124. * 而且预渲染时生成的prefetch标签是modern版本的,低版本浏览器是不需要的
  125. */
  126. config.plugins.delete('prefetch')
  127. pageNames.forEach(page => {
  128. config.plugins.delete(`preload-${page}`)
  129. config.plugins.delete(`prefetch-${page}`)
  130. })
  131. },
  132. transpileDependencies: [
  133. /[/\\]node_modules[/\\]webpack-dev-server[/\\]client[/\\]/,
  134. /[/\\]node_modules[/\\]element-ui[/\\]/,
  135. /[/\\]node_modules[/\\]resize-detector[/\\]/,
  136. ],
  137. // 是否在编译时输出map文件,map文件可以在生产环境提供阅读友好的源码格式而不直接输出源码
  138. productionSourceMap: true,
  139. // css配置
  140. css: {
  141. extract: process.env.NODE_ENV === 'development' || {
  142. // filename必须与chunkFilename层级一致,\node_modules\@vue\cli-service\lib\config\css.js 42行
  143. filename: `${assetsDir}/css/[name]/[name]-[contenthash:8].css`,
  144. chunkFilename: `${assetsDir}/css/chunk/[id]-[contenthash:8].css`,
  145. },
  146. sourceMap: false,
  147. loaderOptions: {},
  148. },
  149. // 所有 webpack-dev-server 的选项都支持
  150. devServer: {
  151. // 启动时打开浏览器
  152. open: true,
  153. // 打开浏览器时的路径
  154. openPage: loginPath.startsWith('/') ? loginPath.substr(1) : loginPath,
  155. // 路径重写规则
  156. historyApiFallback: {
  157. disableDotRule: true,
  158. rewrites: genHistoryApiFallbackRewrites(projectContext, pages),
  159. },
  160. // 服务端口
  161. port: 8888,
  162. // 设置代理
  163. proxy: {
  164. // 将后端请求转发至服务器
  165. [proxyPath]: {
  166. target: backServerRoot,
  167. ws: true,
  168. changeOrigin: true,
  169. pathRewrite (path) {
  170. if (backServerType === 'soa') {
  171. // 单体应用服务器
  172. const pathArr = path.split('/')
  173. // 前端请求/api/whatever/someOtherParts,移除api和之后的部分,不关心此部分内容是什么
  174. pathArr.splice(1, 2)
  175. return pathArr.join('/')
  176. }
  177. // 微服务仅移除掉匹配路径
  178. return path.replace(proxyPath, '')
  179. },
  180. },
  181. [soaBackServerContext]: {
  182. target: backServerRoot,
  183. changeOrigin: true,
  184. pathRewrite (path) {
  185. // jsp页面请求[soaBackServerContext]/someOtherParts,移除上下文
  186. return path.replace(soaBackServerContext, '')
  187. },
  188. },
  189. },
  190. },
  191. // 是否为 Babel 或 TypeScript 使用 thread-loader
  192. parallel: require('os').cpus().length > 1,
  193. // 向 PWA 插件传递选项
  194. pwa: {},
  195. // 可以用来传递任何第三方插件选项
  196. pluginOptions: {},
  197. }
  198. /**
  199. * 重写@vue/cli的方法,使得多页面子路由也可以直接触发路由判断
  200. * @param baseUrl
  201. * @param pages
  202. * @returns {*[]}
  203. */
  204. function genHistoryApiFallbackRewrites (baseUrl, pages = {}) {
  205. const path = require('path')
  206. const multiPageRewrites = Object.keys(pages)
  207. // sort by length in reversed order to avoid overrides
  208. // eg. 'page11' should appear in front of 'page1'
  209. .sort((a, b) => b.length - a.length).map(name => ({
  210. from: new RegExp(`^${baseUrl}/${name}/`), // 此处被重写
  211. to: path.posix.join(baseUrl, pages[name].filename || `${name}/index.html`),
  212. }))
  213. return [
  214. ...multiPageRewrites,
  215. // 平台页面
  216. ...platformPages.map(name => ({
  217. from: new RegExp(`^${baseUrl}/${name}/`),
  218. to: path.posix.join(baseUrl, `${name}/index.html`),
  219. })),
  220. // 系统根路径,进管理页面
  221. {
  222. from: new RegExp(`^${baseUrl}(/?|/error-.*)$`),
  223. to: path.posix.join(baseUrl, 'admin'),
  224. },
  225. // 服务器根路径,进静态404
  226. { from: /\//, to: '/common-assets/pages/error/404.html' },
  227. // 其他,进路由404
  228. { from: /./, to: path.posix.join(baseUrl, '/admin/404') },
  229. ]
  230. }