这篇基本上转载自滴滴前端团队的一片文章,感谢这样优秀的前端团队.

目录结构

  1. .
  2. ├── README.md
  3. ├── build
  4. ├── build.js
  5. ├── check-versions.js
  6. ├── dev-client.js
  7. ├── dev-server.js
  8. ├── utils.js
  9. ├── webpack.base.conf.js
  10. ├── webpack.dev.conf.js
  11. └── webpack.prod.conf.js
  12. ├── config
  13. ├── dev.env.js
  14. ├── index.js
  15. └── prod.env.js
  16. ├── index.html
  17. ├── package.json
  18. ├── src
  19. ├── App.vue
  20. ├── assets
  21. └── logo.png
  22. ├── components
  23. └── Hello.vue
  24. └── main.js
  25. └── static

package.json

我们可以看到

  1. "scripts": {
  2. "dev": "node build/dev-server.js",
  3. "build": "node build/build.js",
  4. "lint": "eslint --ext .js,.vue src"
  5. }

当我们执行 npm run dev / npm run build 时运行的是 node build/dev-server.js 或 node build/build.js

dev-server.js

  1. // 检查 Node 和 npm 版本
  2. require('./check-versions')()
  3. // 获取 config/index.js 的默认配置
  4. var config = require('../config')
  5. // 如果 Node 的环境无法判断当前是 dev / product 环境
  6. // 使用 config.dev.env.NODE_ENV 作为当前的环境
  7. if (!process.env.NODE_ENV) process.env.NODE_ENV = JSON.parse(config.dev.env.NODE_ENV)
  8. // 使用 NodeJS 自带的文件路径工具
  9. var path = require('path')
  10. // 使用 express
  11. var express = require('express')
  12. // 使用 webpack
  13. var webpack = require('webpack')
  14. // 一个可以强制打开浏览器并跳转到指定 url 的插件
  15. var opn = require('opn')
  16. // 使用 proxyTable
  17. var proxyMiddleware = require('http-proxy-middleware')
  18. // 使用 dev 环境的 webpack 配置
  19. var webpackConfig = require('./webpack.dev.conf')
  20. // default port where dev server listens for incoming traffic
  21. // 如果没有指定运行端口,使用 config.dev.port 作为运行端口
  22. var port = process.env.PORT || config.dev.port
  23. // Define HTTP proxies to your custom API backend
  24. // https://github.com/chimurai/http-proxy-middleware
  25. // 使用 config.dev.proxyTable 的配置作为 proxyTable 的代理配置
  26. var proxyTable = config.dev.proxyTable
  27. // 使用 express 启动一个服务
  28. var app = express()
  29. // 启动 webpack 进行编译
  30. var compiler = webpack(webpackConfig)
  31. // 启动 webpack-dev-middleware,将 编译后的文件暂存到内存中
  32. var devMiddleware = require('webpack-dev-middleware')(compiler, {
  33. publicPath: webpackConfig.output.publicPath,
  34. stats: {
  35. colors: true,
  36. chunks: false
  37. }
  38. })
  39. // 启动 webpack-hot-middleware,也就是我们常说的 Hot-reload
  40. var hotMiddleware = require('webpack-hot-middleware')(compiler)
  41. // force page reload when html-webpack-plugin template changes
  42. compiler.plugin('compilation', function (compilation) {
  43. compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) {
  44. hotMiddleware.publish({ action: 'reload' })
  45. cb()
  46. })
  47. })
  48. // proxy api requests
  49. // 将 proxyTable 中的请求配置挂在到启动的 express 服务上
  50. Object.keys(proxyTable).forEach(function (context) {
  51. var options = proxyTable[context]
  52. if (typeof options === 'string') {
  53. options = { target: options }
  54. }
  55. app.use(proxyMiddleware(context, options))
  56. })
  57. // handle fallback for HTML5 history API
  58. // 使用 connect-history-api-fallback 匹配资源,如果不匹配就可以重定向到指定地址
  59. app.use(require('connect-history-api-fallback')())
  60. // serve webpack bundle output
  61. // 将暂存到内存中的 webpack 编译后的文件挂在到 express 服务上
  62. app.use(devMiddleware)
  63. // enable hot-reload and state-preserving
  64. // compilation error display
  65. // 将 Hot-reload 挂在到 express 服务上
  66. app.use(hotMiddleware)
  67. // serve pure static assets
  68. // 拼接 static 文件夹的静态资源路径
  69. var staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory)
  70. // 为静态资源提供响应服务
  71. app.use(staticPath, express.static('./static'))
  72. // 让我们这个 express 服务监听 port 的请求,并且将此服务作为 dev-server.js 的接口暴露
  73. module.exports = app.listen(port, function (err) {
  74. if (err) {
  75. console.log(err)
  76. return
  77. }
  78. var uri = 'http://localhost:' + port
  79. console.log('Listening at ' + uri + '\n')
  80. // when env is testing, don't need open it
  81. // 如果不是测试环境,自动打开浏览器并跳到我们的开发地址
  82. if (process.env.NODE_ENV !== 'testing') {
  83. opn(uri)
  84. }
  85. })

webpack.dev.conf.js

  1. // 同样的使用了 config/index.js
  2. var config = require('../config')
  3. // 使用 webpack
  4. var webpack = require('webpack')
  5. // 使用 webpack 配置合并插件
  6. var merge = require('webpack-merge')
  7. // 使用一些小工具
  8. var utils = require('./utils')
  9. // 加载 webpack.base.conf
  10. var baseWebpackConfig = require('./webpack.base.conf')
  11. // 使用 html-webpack-plugin 插件,这个插件可以帮我们自动生成 html 并且注入到 .html 文件中
  12. var HtmlWebpackPlugin = require('html-webpack-plugin')
  13. // add hot-reload related code to entry chunks
  14. // 将 Hol-reload 相对路径添加到 webpack.base.conf 的 对应 entry 前
  15. Object.keys(baseWebpackConfig.entry).forEach(function (name) {
  16. baseWebpackConfig.entry[name] = ['./build/dev-client'].concat(baseWebpackConfig.entry[name])
  17. })
  18. // 将我们 webpack.dev.conf.js 的配置和 webpack.base.conf.js 的配置合并
  19. module.exports = merge(baseWebpackConfig, {
  20. module: {
  21. // 使用 styleLoaders
  22. loaders: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap })
  23. },
  24. // eval-source-map is faster for development
  25. // 使用 #eval-source-map 模式作为开发工具,此配置可参考 DDFE 往期文章详细了解
  26. devtool: '#eval-source-map',
  27. plugins: [
  28. // definePlugin 接收字符串插入到代码当中, 所以你需要的话可以写上 JS 的字符串
  29. new webpack.DefinePlugin({
  30. 'process.env': config.dev.env
  31. }),
  32. // https://github.com/glenjamin/webpack-hot-middleware#installation--usage
  33. new webpack.optimize.OccurenceOrderPlugin(),
  34. // HotModule 插件在页面进行变更的时候只会重回对应的页面模块,不会重绘整个 html 文件
  35. new webpack.HotModuleReplacementPlugin(),
  36. // 使用了 NoErrorsPlugin 后页面中的报错不会阻塞,但是会在编译结束后报错
  37. new webpack.NoErrorsPlugin(),
  38. // https://github.com/ampedandwired/html-webpack-plugin
  39. // 将 index.html 作为入口,注入 html 代码后生成 index.html文件
  40. new HtmlWebpackPlugin({
  41. filename: 'index.html',
  42. template: 'index.html',
  43. inject: true
  44. })
  45. ]
  46. })

webpack.base.conf.js

我们看到在 webpack.dev.conf.js 中又引入了 webpack.base.conf.js

  1. // 使用 NodeJS 自带的文件路径插件
  2. var path = require('path')
  3. // 引入 config/index.js
  4. var config = require('../config')
  5. // 引入一些小工具
  6. var utils = require('./utils')
  7. // 拼接我们的工作区路径为一个绝对路径
  8. var projectRoot = path.resolve(__dirname, '../')
  9. // 将 NodeJS 环境作为我们的编译环境
  10. var env = process.env.NODE_ENV
  11. // check env & config/index.js to decide weither to enable CSS Sourcemaps for the
  12. // various preprocessor loaders added to vue-loader at the end of this file
  13. // 是否在 dev 环境下开启 cssSourceMap ,在 config/index.js 中可配置
  14. var cssSourceMapDev = (env === 'development' && config.dev.cssSourceMap)
  15. // 是否在 production 环境下开启 cssSourceMap ,在 config/index.js 中可配置
  16. var cssSourceMapProd = (env === 'production' && config.build.productionSourceMap)
  17. // 最终是否使用 cssSourceMap
  18. var useCssSourceMap = cssSourceMapDev || cssSourceMapProd
  19. module.exports = {
  20. entry: {
  21. // 编译文件入口
  22. app: './src/main.js'
  23. },
  24. output: {
  25. // 编译输出的根路径
  26. path: config.build.assetsRoot,
  27. // 正式发布环境下编译输出的发布路径
  28. publicPath: process.env.NODE_ENV === 'production' ? config.build.assetsPublicPath : config.dev.assetsPublicPath,
  29. // 编译输出的文件名
  30. filename: '[name].js'
  31. },
  32. resolve: {
  33. // 自动补全的扩展名
  34. extensions: ['', '.js', '.vue'],
  35. // 不进行自动补全或处理的文件或者文件夹
  36. fallback: [path.join(__dirname, '../node_modules')],
  37. alias: {
  38. // 默认路径代理,例如 import Vue from 'vue',会自动到 'vue/dist/vue.common.js'中寻找
  39. 'vue': 'vue/dist/vue.common.js',
  40. 'src': path.resolve(__dirname, '../src'),
  41. 'assets': path.resolve(__dirname, '../src/assets'),
  42. 'components': path.resolve(__dirname, '../src/components')
  43. }
  44. },
  45. resolveLoader: {
  46. fallback: [path.join(__dirname, '../node_modules')]
  47. },
  48. module: {
  49. preLoaders: [
  50. // 预处理的文件及使用的 loader
  51. {
  52. test: /\.vue$/,
  53. loader: 'eslint',
  54. include: projectRoot,
  55. exclude: /node_modules/
  56. },
  57. {
  58. test: /\.js$/,
  59. loader: 'eslint',
  60. include: projectRoot,
  61. exclude: /node_modules/
  62. }
  63. ],
  64. loaders: [
  65. // 需要处理的文件及使用的 loader
  66. {
  67. test: /\.vue$/,
  68. loader: 'vue'
  69. },
  70. {
  71. test: /\.js$/,
  72. loader: 'babel',
  73. include: projectRoot,
  74. exclude: /node_modules/
  75. },
  76. {
  77. test: /\.json$/,
  78. loader: 'json'
  79. },
  80. {
  81. test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
  82. loader: 'url',
  83. query: {
  84. limit: 10000,
  85. name: utils.assetsPath('img/[name].[hash:7].[ext]')
  86. }
  87. },
  88. {
  89. test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
  90. loader: 'url',
  91. query: {
  92. limit: 10000,
  93. name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
  94. }
  95. }
  96. ]
  97. },
  98. eslint: {
  99. // eslint 代码检查配置工具
  100. formatter: require('eslint-friendly-formatter')
  101. },
  102. vue: {
  103. // .vue 文件配置 loader 及工具 (autoprefixer)
  104. loaders: utils.cssLoaders({ sourceMap: useCssSourceMap }),
  105. postcss: [
  106. require('autoprefixer')({
  107. browsers: ['last 2 versions']
  108. })
  109. ]
  110. }
  111. }

config/index.js

终于分析完了 webpack.base.conf.js,来让我们看一下 config/index.js
index.js 中有 dev 和 production 两种环境的配置

  1. // see http://vuejs-templates.github.io/webpack for documentation.
  2. // 不再重复介绍了 ...
  3. var path = require('path')
  4. module.exports = {
  5. // production 环境
  6. build: {
  7. // 使用 config/prod.env.js 中定义的编译环境
  8. env: require('./prod.env'),
  9. index: path.resolve(__dirname, '../dist/index.html'), // 编译输入的 index.html 文件
  10. // 编译输出的静态资源根路径
  11. assetsRoot: path.resolve(__dirname, '../dist'),
  12. // 编译输出的二级目录
  13. assetsSubDirectory: 'static',
  14. // 编译发布上线路径的根目录,可配置为资源服务器域名或 CDN 域名
  15. assetsPublicPath: '/',
  16. // 是否开启 cssSourceMap
  17. productionSourceMap: true,
  18. // Gzip off by default as many popular static hosts such as
  19. // Surge or Netlify already gzip all static assets for you.
  20. // Before setting to `true`, make sure to:
  21. // npm install --save-dev compression-webpack-plugin
  22. // 是否开启 gzip
  23. productionGzip: false,
  24. // 需要使用 gzip 压缩的文件扩展名
  25. productionGzipExtensions: ['js', 'css']
  26. },
  27. // dev 环境
  28. dev: {
  29. // 使用 config/dev.env.js 中定义的编译环境
  30. env: require('./dev.env'),
  31. // 运行测试页面的端口
  32. port: 8080,
  33. // 编译输出的二级目录
  34. assetsSubDirectory: 'static',
  35. // 编译发布上线路径的根目录,可配置为资源服务器域名或 CDN 域名
  36. assetsPublicPath: '/',
  37. // 需要 proxyTable 代理的接口(可跨域)
  38. proxyTable: {},
  39. // CSS Sourcemaps off by default because relative paths are "buggy"
  40. // with this option, according to the CSS-Loader README
  41. // (https://github.com/webpack/css-loader#sourcemaps)
  42. // In our experience, they generally work as expected,
  43. // just be aware of this issue when enabling this option.
  44. // 是否开启 cssSourceMap
  45. cssSourceMap: false
  46. }
  47. }

至此,我们的 npm run dev 命令就讲解完毕,
下面让我们来看一看执行 npm run build 命令时发生了什么

build.js

  1. // https://github.com/shelljs/shelljs
  2. // 检查 Node 和 npm 版本
  3. require('./check-versions')()
  4. // 使用了 shelljs 插件,可以让我们在 node 环境的 js 中使用 shell
  5. require('shelljs/global')
  6. env.NODE_ENV = 'production'
  7. // 不再赘述
  8. var path = require('path')
  9. // 加载 config.js
  10. var config = require('../config')
  11. // 一个很好看的 loading 插件
  12. var ora = require('ora')
  13. // 加载 webpack
  14. var webpack = require('webpack')
  15. // 加载 webpack.prod.conf
  16. var webpackConfig = require('./webpack.prod.conf')
  17. // 输出提示信息 ~ 提示用户请在 http 服务下查看本页面,否则为空白页
  18. console.log(
  19. ' Tip:\n' +
  20. ' Built files are meant to be served over an HTTP server.\n' +
  21. ' Opening index.html over file:// won\'t work.\n'
  22. )
  23. // 使用 ora 打印出 loading + log
  24. var spinner = ora('building for production...')
  25. // 开始 loading 动画
  26. spinner.start()
  27. // 拼接编译输出文件路径
  28. var assetsPath = path.join(config.build.assetsRoot, config.build.assetsSubDirectory)
  29. // 删除这个文件夹 (递归删除)
  30. rm('-rf', assetsPath)
  31. // 创建此文件夹
  32. mkdir('-p', assetsPath)
  33. // 复制 static 文件夹到我们的编译输出目录
  34. cp('-R', 'static/*', assetsPath)
  35. // 开始 webpack 的编译
  36. webpack(webpackConfig, function (err, stats) {
  37. // 编译成功的回调函数
  38. spinner.stop()
  39. if (err) throw err
  40. process.stdout.write(stats.toString({
  41. colors: true,
  42. modules: false,
  43. children: false,
  44. chunks: false,
  45. chunkModules: false
  46. }) + '\n')
  47. })

webpack.prod.conf.js

  1. // 不再赘述
  2. var path = require('path')
  3. // 加载 confi.index.js
  4. var config = require('../config')
  5. // 使用一些小工具
  6. var utils = require('./utils')
  7. // 加载 webpack
  8. var webpack = require('webpack')
  9. // 加载 webpack 配置合并工具
  10. var merge = require('webpack-merge')
  11. // 加载 webpack.base.conf.js
  12. var baseWebpackConfig = require('./webpack.base.conf')
  13. // 一个 webpack 扩展,可以提取一些代码并且将它们和文件分离开
  14. // 如果我们想将 webpack 打包成一个文件 css js 分离开,那我们需要这个插件
  15. var ExtractTextPlugin = require('extract-text-webpack-plugin')
  16. // 一个可以插入 html 并且创建新的 .html 文件的插件
  17. var HtmlWebpackPlugin = require('html-webpack-plugin')
  18. var env = config.build.env
  19. // 合并 webpack.base.conf.js
  20. var webpackConfig = merge(baseWebpackConfig, {
  21. module: {
  22. // 使用的 loader
  23. loaders: utils.styleLoaders({ sourceMap: config.build.productionSourceMap, extract: true })
  24. },
  25. // 是否使用 #source-map 开发工具,更多信息可以查看 DDFE 往期文章
  26. devtool: config.build.productionSourceMap ? '#source-map' : false,
  27. output: {
  28. // 编译输出目录
  29. path: config.build.assetsRoot,
  30. // 编译输出文件名
  31. // 我们可以在 hash 后加 :6 决定使用几位 hash 值
  32. filename: utils.assetsPath('js/[name].[chunkhash].js'),
  33. // 没有指定输出名的文件输出的文件名
  34. chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
  35. },
  36. vue: {
  37. // 编译 .vue 文件时使用的 loader
  38. loaders: utils.cssLoaders({
  39. sourceMap: config.build.productionSourceMap,
  40. extract: true
  41. })
  42. },
  43. plugins: [
  44. // 使用的插件
  45. // http://vuejs.github.io/vue-loader/en/workflow/production.html
  46. // definePlugin 接收字符串插入到代码当中, 所以你需要的话可以写上 JS 的字符串
  47. new webpack.DefinePlugin({
  48. 'process.env': env
  49. }),
  50. // 压缩 js (同样可以压缩 css)
  51. new webpack.optimize.UglifyJsPlugin({
  52. compress: {
  53. warnings: false
  54. }
  55. }),
  56. new webpack.optimize.OccurrenceOrderPlugin(),
  57. // extract css into its own file
  58. // 将 css 文件分离出来
  59. new ExtractTextPlugin(utils.assetsPath('css/[name].[contenthash].css')),
  60. // generate dist index.html with correct asset hash for caching.
  61. // you can customize output by editing /index.html
  62. // see https://github.com/ampedandwired/html-webpack-plugin
  63. // 输入输出的 .html 文件
  64. new HtmlWebpackPlugin({
  65. filename: config.build.index,
  66. template: 'index.html',
  67. // 是否注入 html
  68. inject: true,
  69. // 压缩的方式
  70. minify: {
  71. removeComments: true,
  72. collapseWhitespace: true,
  73. removeAttributeQuotes: true
  74. // more options:
  75. // https://github.com/kangax/html-minifier#options-quick-reference
  76. },
  77. // necessary to consistently work with multiple chunks via CommonsChunkPlugin
  78. chunksSortMode: 'dependency'
  79. }),
  80. // split vendor js into its own file
  81. // 没有指定输出文件名的文件输出的静态文件名
  82. new webpack.optimize.CommonsChunkPlugin({
  83. name: 'vendor',
  84. minChunks: function (module, count) {
  85. // any required modules inside node_modules are extracted to vendor
  86. return (
  87. module.resource &&
  88. /\.js$/.test(module.resource) &&
  89. module.resource.indexOf(
  90. path.join(__dirname, '../node_modules')
  91. ) === 0
  92. )
  93. }
  94. }),
  95. // extract webpack runtime and module manifest to its own file in order to
  96. // prevent vendor hash from being updated whenever app bundle is updated
  97. // 没有指定输出文件名的文件输出的静态文件名
  98. new webpack.optimize.CommonsChunkPlugin({
  99. name: 'manifest',
  100. chunks: ['vendor']
  101. })
  102. ]
  103. })
  104. // 开启 gzip 的情况下使用下方的配置
  105. if (config.build.productionGzip) {
  106. // 加载 compression-webpack-plugin 插件
  107. var CompressionWebpackPlugin = require('compression-webpack-plugin')
  108. // 向webpackconfig.plugins中加入下方的插件
  109. var reProductionGzipExtensions = '\\.(' + config.build.productionGzipExtensions.join('|') + '$)'
  110. webpackConfig.plugins.push(
  111. // 使用 compression-webpack-plugin 插件进行压缩
  112. new CompressionWebpackPlugin({
  113. asset: '[path].gz[query]',
  114. algorithm: 'gzip',
  115. test: new RegExp(reProductionGzipExtensions), // 注:此处因有代码格式化的bug,与源码有差异
  116. threshold: 10240,
  117. minRatio: 0.8
  118. })
  119. )
  120. }
  121. module.exports = webpackConfig



谢谢支持




扫一扫,分享到微信