前言
已经有很多分析
Vue-cli
搭建工程的文章,为什么自己还要写一遍呢。学习就好比是座大山,人们沿着不同的路登山,分享着自己看到的风景。你不一定能看到别人看到的风景,体会到别人的心情。只有自己去登山,才能看到不一样的风景,体会才更加深刻。
正文从这里开始~
使用vue-cli
初始化webpack
工程
// # 安装npm install -g vue-cli// 安装完后vue命令就可以使用了。实际上是全局注册了vue、vue-init、vue-list几个命令// # ubuntu 系统下// [vue-cli@2.9.3] link /usr/local/bin/vue@ -> /usr/local/lib/node_modules/vue-cli/bin/vue// [vue-cli@2.9.3] link /usr/local/bin/vue-init@ -> /usr/local/lib/node_modules/vue-cli/bin/vue-init// [vue-cli@2.9.3] link /usr/local/bin/vue-list@ -> /usr/local/lib/node_modules/vue-cli/bin/vue-listvue list// 可以发现有browserify、browserify-simple、pwa、simple、webpack、webpack-simple几种模板可选,这里选用webpack。// # 使用 vue initvue init <template-name> <project-name>// # 例子vue init webpack analyse-vue-cli
更多vue-cli
如何工作的可以查看这篇文章vue-cli是如何工作的,或者分析Vue-cli源码查看这篇走进Vue-cli源码,自己动手搭建前端脚手架工具,再或者直接查看vue-cli github仓库源码
如果对webpack
还不是很了解,可以查看webpack官方文档中的概念,虽然是最新版本的,但概念都是差不多的。
package.json
分析一个项目,一般从package.json
的命令入口scripts
开始。
"scripts": { // dev webpack-dev-server --inline 模式 --progress 显示进度 --config 指定配置文件(默认是webpack.config.js) "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js", "start": "npm run dev", // jest测试 "unit": "jest --config test/unit/jest.conf.js --coverage", // e2e测试 "e2e": "node test/e2e/runner.js", // 运行jest测试和e2e测试 "test": "npm run unit && npm run e2e", // eslint --ext 指定扩展名和相应的文件 "lint": "eslint --ext .js,.vue src test/unit test/e2e/specs", // node 执行build/build.js文件 "build": "node build/build.js"},
Npm Script
底层实现原理是通过调用 Shell
去运行脚本命令。npm run start
等同于运行npm run dev
。
Npm Script
还有一个重要的功能是能运行安装到项目目录里的 node_modules
里的可执行模块。
例如在通过命令npm i -D webpack-dev-server
将webpack-dev-server
安装到项目后,是无法直接在项目根目录下通过命令 webpack-dev-server
去执行 webpack-dev-server
构建的,而是要通过命令 ./node_modules/.bin/webpack-dev-server
去执行。
Npm Script
能方便的解决这个问题,只需要在 scripts
字段里定义一个任务,例如:
"dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js"
Npm Script
会先去项目目录下的 node_modules
中寻找有没有可执行的 webpack-dev-server
文件,如果有就使用本地的,如果没有就使用全局的。 所以现在执行 webpack-dev-server
启动服务时只需要通过执行 npm run dev
去实现。
再来看下 npm run dev
webpack-dev-server
其实是一个node.js
的应用程序,它是通过JavaScript
开发的。在命令行执行npm run dev
命令等同于执行node ./node_modules/webpack-dev-server/bin/webpack-dev-server.js --inline --progress --config build/webpack.dev.conf.js
。你可以试试。
更多package.json
的配置项,可以查看阮一峰老师的文章 package.json文件
npm run dev
指定了build/webpack.dev.conf.js
配置去启动服务,那么我们来看下这个文件做了什么。
build/webpack.dev.conf.js
webpack
开发环境配置
这个文件主要做了以下几件事情:
1、引入各种依赖,同时也引入了config
文件夹下的变量和配置,和一个工具函数build/utils.js
,
2、合并build/webpack.base.conf.js
配置文件,
3、配置开发环境一些devServer
,plugin
等配置,
4、最后导出了一个Promise
,根据配置的端口,寻找可用的端口来启动服务。
具体可以看build/webpack.dev.conf.js
这个文件注释:
'use strict'// 引入工具函数const utils = require('./utils')// 引入webpackconst webpack = require('webpack')// 引入config/index.js配置const config = require('../config')// 合并webpack配置const merge = require('webpack-merge')const path = require('path')// 基本配置const baseWebpackConfig = require('./webpack.base.conf')// 拷贝插件const CopyWebpackPlugin = require('copy-webpack-plugin')// 生成html的插件const HtmlWebpackPlugin = require('html-webpack-plugin')// 友好提示的插件 https://github.com/geowarin/friendly-errors-webpack-pluginconst FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')// 查找可用端口 // github仓库 https://github.com/indexzero/node-portfinderconst portfinder = require('portfinder')// process模块用来与当前进程互动,可以通过全局变量process访问,不必使用require命令加载。它是一个EventEmitter对象的实例。// 后面有些process模块用到的,所以这里统一列举下。// 更多查看这篇阮一峰的这篇文章 http://javascript.ruanyifeng.com/nodejs/process.html// process对象提供一系列属性,用于返回系统信息。// process.pid:当前进程的进程号。// process.version:Node的版本,比如v0.10.18。// process.platform:当前系统平台,比如Linux。// process.title:默认值为“node”,可以自定义该值。// process.argv:当前进程的命令行参数数组。// process.env:指向当前shell的环境变量,比如process.env.HOME。// process.execPath:运行当前进程的可执行文件的绝对路径。// process.stdout:指向标准输出。// process.stdin:指向标准输入。// process.stderr:指向标准错误。// process对象提供以下方法:// process.exit():退出当前进程。// process.cwd():返回运行当前脚本的工作目录的路径。_// process.chdir():改变工作目录。// process.nextTick():将一个回调函数放在下次事件循环的顶部。// hostconst HOST = process.env.HOST// 端口const PORT = process.env.PORT && Number(process.env.PORT)// 合并基本的webpack配置const devWebpackConfig = merge(baseWebpackConfig, { module: { // cssSourceMap这里配置的是true rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true }) }, // cheap-module-eval-source-map is faster for development // 在开发环境是cheap-module-eval-source-map选项更快 // 这里配置的是cheap-module-eval-source-map // 更多可以查看中文文档:https://webpack.docschina.org/configuration/devtool/#devtool // 英文 https://webpack.js.org/configuration/devtool/#development devtool: config.dev.devtool, // these devServer options should be customized in /config/index.js devServer: { // 配置在客户端的日志等级,这会影响到你在浏览器开发者工具控制台里看到的日志内容。 // clientLogLevel 是枚举类型,可取如下之一的值 none | error | warning | info。 // 默认为 info 级别,即输出所有类型的日志,设置成 none 可以不输出任何日志。 clientLogLevel: 'warning', // historyApiFallback boolean object 用于方便的开发使用了 HTML5 History API 的单页应用。 // 可以简单true 或者 任意的 404 响应可以提供为 index.html 页面。 historyApiFallback: { rewrites: [ // config.dev.assetsPublicPath 这里是 / { from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, 'index.html') }, ], }, // 开启热更新 hot: true, // contentBase 配置 DevServer HTTP 服务器的文件根目录。 // 默认情况下为当前执行目录,通常是项目根目录,所有一般情况下你不必设置它,除非你有额外的文件需要被 DevServer 服务。 contentBase: false, // since we use CopyWebpackPlugin. // compress 配置是否启用 gzip 压缩。boolean 为类型,默认为 false。 compress: true, // host // 例如你想要局域网中的其它设备访问你本地的服务,可以在启动 DevServer 时带上 --host 0.0.0.0 // 或者直接设置为 0.0.0.0 // 这里配置的是localhost host: HOST || config.dev.host, // 端口号 这里配置的是8080 port: PORT || config.dev.port, // 打开浏览器,这里配置是不打开false open: config.dev.autoOpenBrowser, // 是否在浏览器以遮罩形式显示报错信息 这里配置的是true overlay: config.dev.errorOverlay ? { warnings: false, errors: true } : false, // 这里配置的是 / publicPath: config.dev.assetsPublicPath, // 代理 这里配置的是空{},有需要可以自行配置 proxy: config.dev.proxyTable, // 启用 quiet 后,除了初始启动信息之外的任何内容都不会被打印到控制台。这也意味着来自 webpack 的错误或警告在控制台不可见。 // 开启后一般非常干净只有类似的提示 Your application is running here: http://localhost:8080 quiet: true, // necessary for FriendlyErrorsPlugin // webpack-dev-middleware // watch: false, // 启用 Watch 模式。这意味着在初始构建之后,webpack 将继续监听任何已解析文件的更改。Watch 模式默认关闭。 // webpack-dev-server 和 webpack-dev-middleware 里 Watch 模式默认开启。 // Watch 模式的选项 watchOptions: { // 或者指定毫秒为单位进行轮询。 // 这里配置为false poll: config.dev.poll, } // 更多查看中文文档:https://webpack.docschina.org/configuration/watch/#src/components/Sidebar/Sidebar.jsx }, plugins: [ // 定义为开发环境 new webpack.DefinePlugin({ // 这里是 { NODE_ENV: '"development"' } 'process.env': require('../config/dev.env') }), // 热更新插件 new webpack.HotModuleReplacementPlugin(), // 热更新时显示具体的模块路径 new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update. // 在编译出现错误时,使用 NoEmitOnErrorsPlugin 来跳过输出阶段。 new webpack.NoEmitOnErrorsPlugin(), // github仓库 https://github.com/ampedandwired/html-webpack-plugin new HtmlWebpackPlugin({ filename: 'index.html', template: 'index.html', // inject 默认值 true,script标签位于html文件的 body 底部 // body 通true, header, script 标签位于 head 标签内 // false 不插入生成的 js 文件,只是单纯的生成一个 html 文件 inject: true }), // copy custom static assets // 把static资源复制到相应目录。 new CopyWebpackPlugin([ { // 这里是 static from: path.resolve(__dirname, '../static'), // 这里是 static to: config.dev.assetsSubDirectory, // 忽略.开头的文件。比如这里的.gitkeep,这个文件是指空文件夹也提交到git ignore: ['.*'] } ]) ] })// 导出一个promisemodule.exports = new Promise((resolve, reject) => { // process.env.PORT 可以在命令行指定端口号,比如PORT=2000 npm run dev,那访问就是http://localhost:2000 // config.dev.port 这里配置是 8080 portfinder.basePort = process.env.PORT || config.dev.port // 以配置的端口为基准,寻找可用的端口,比如:如果8080占用,那就8081,以此类推 // github仓库 https://github.com/indexzero/node-portfinder portfinder.getPort((err, port) => { if (err) { reject(err) } else { // publish the new Port, necessary for e2e tests process.env.PORT = port // add port to devServer config devWebpackConfig.devServer.port = port // Add FriendlyErrorsPlugin devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({ compilationSuccessInfo: { messages: [`Your application is running here: http://${devWebpackConfig.devServer.host}:${port}`], }, // notifyOnErrors 这里配置是 true // onErrors 是一个函数,出错输出错误信息,系统原生的通知 onErrors: config.dev.notifyOnErrors ? utils.createNotifierCallback() : undefined })) resolve(devWebpackConfig) } }) })
build/utils.js
工具函数
上文build/webpack.dev.conf.js
提到引入了build/utils.js
工具函数。
该文件主要写了以下几个工具函数:
1、assetsPath
返回输出路径,
2、cssLoaders
返回相应的css-loader
配置,
3、styleLoaders
返回相应的处理样式的配置,
4、createNotifierCallback
创建启动服务时出错时提示信息回调。
具体配置可以看该文件注释:
'use strict'const path = require('path')// 引入配置文件config/index.jsconst config = require('../config')// 提取css的插件const ExtractTextPlugin = require('extract-text-webpack-plugin')// 引入package.json配置const packageConfig = require('../package.json')// 返回路径exports.assetsPath = function (_path) { const assetsSubDirectory = process.env.NODE_ENV === 'production' // 二级目录 这里是 static ? config.build.assetsSubDirectory // 二级目录 这里是 static : config.dev.assetsSubDirectory // 生成跨平台兼容的路径 // 更多查看Node API链接:https://nodejs.org/api/path.html#path_path_posix return path.posix.join(assetsSubDirectory, _path) } exports.cssLoaders = function (options) { // 作为参数传递进来的options对象 // { // // sourceMap这里是true // sourceMap: true, // // 是否提取css到单独的css文件 // extract: true, // // 是否使用postcss // usePostCSS: true // } options = options || {} const cssLoader = { loader: 'css-loader', options: { sourceMap: options.sourceMap } } const postcssLoader = { loader: 'postcss-loader', options: { sourceMap: options.sourceMap } } // generate loader string to be used with extract text plugin // 创建对应的loader配置 function generateLoaders (loader, loaderOptions) { // 是否使用usePostCSS,来决定是否采用postcssLoader const loaders = options.usePostCSS ? [cssLoader, postcssLoader] : [cssLoader] if (loader) { loaders.push({ loader: loader + '-loader', // 合并 loaderOptions 生成options options: Object.assign({}, loaderOptions, { sourceMap: options.sourceMap }) }) } // Extract CSS when that option is specified // (which is the case during production build) if (options.extract) { // 如果提取使用ExtractTextPlugin插件提取 // 更多配置 看插件中文文档:https://webpack.docschina.org/plugins/extract-text-webpack-plugin/ return ExtractTextPlugin.extract({ // 指需要什么样的loader去编译文件 // loader 被用于将资源转换成一个 CSS 导出模块 (必填) use: loaders, // loader(例如 'style-loader')应用于当 CSS 没有被提取(也就是一个额外的 chunk,当 allChunks: false) fallback: 'vue-style-loader' }) } else { return ['vue-style-loader'].concat(loaders) } } // https://vue-loader.vuejs.org/en/configurations/extract-css.html return { css: generateLoaders(), postcss: generateLoaders(), less: generateLoaders('less'), // sass indentedSyntax 语法缩进,类似下方格式 // #main // color: blue // font-size: 0.3em sass: generateLoaders('sass', { indentedSyntax: true }), scss: generateLoaders('sass'), stylus: generateLoaders('stylus'), styl: generateLoaders('stylus') } }// Generate loaders for standalone style files (outside of .vue)// 最终会返回webpack css相关的配置exports.styleLoaders = function (options) { // { // // sourceMap这里是true // sourceMap: true, // // 是否提取css到单独的css文件 // extract: true, // // 是否使用postcss // usePostCSS: true // } const output = [] const loaders = exports.cssLoaders(options) for (const extension in loaders) { const loader = loaders[extension] output.push({ test: new RegExp('\\.' + extension + '$'), use: loader }) } return output }// npm run dev 出错时, FriendlyErrorsPlugin插件 配置 onErrors输出错误信息exports.createNotifierCallback = () => { // 'node-notifier'是一个跨平台系统通知的页面,当遇到错误时,它能用系统原生的推送方式给你推送信息 const notifier = require('node-notifier') return (severity, errors) => { if (severity !== 'error') return const error = errors[0] const filename = error.file && error.file.split('!').pop() notifier.notify({ title: packageConfig.name, message: severity + ': ' + error.name, subtitle: filename || '', icon: path.join(__dirname, 'logo.png') }) } }
build/webpack.base.conf.js
webpack
基本配置文件
上文build/webpack.dev.conf.js
提到引入了build/webpack.base.conf.js
这个webpack
基本配置文件。
这个文件主要做了以下几件事情:
1、引入各种插件、配置等,其中引入了build/vue-loader.conf.js
相关配置,
2、创建eslint
规则配置,默认启用,
3、导出webpack
配置对象,其中包含context
,入口entry
,输出output
,resolve
,module
下的rules
(处理对应文件的规则),和node
相关的配置等。
具体可以看这个文件注释:
// 使用严格模式,更多严格模式可以查看// [阮一峰老师的es标准入门](http://es6.ruanyifeng.com/?search=%E4%B8%A5%E6%A0%BC%E6%A8%A1%E5%BC%8F&x=0&y=0#docs/function#%E4%B8%A5%E6%A0%BC%E6%A8%A1%E5%BC%8F)'use strict'const path = require('path')// 引入工具函数const utils = require('./utils')// 引入配置文件,也就是config/index.js文件const config = require('../config')// 引入vue-loader的配置文件const vueLoaderConfig = require('./vue-loader.conf')// 定义获取绝对路径函数function resolve (dir) { return path.join(__dirname, '..', dir) }// 创建eslint配置const createLintingRule = () => ({ test: /\.(js|vue)$/, loader: 'eslint-loader', // 执行顺序,前置,还有一个选项是post是后置 // 把 eslint-loader 的执行顺序放到最前面,防止其它 Loader 把处理后的代码交给 eslint-loader 去检查 enforce: 'pre', // 包含文件夹 include: [resolve('src'), resolve('test')], options: { // 使用友好的eslint提示插件 formatter: require('eslint-friendly-formatter'), // eslint报错提示是否显示以遮罩形式显示在浏览器中 // 这里showEslintErrorsInOverlay配置是false emitWarning: !config.dev.showEslintErrorsInOverlay } })module.exports = { // 运行环境的上下文,就是实际的目录,也就是项目根目录 context: path.resolve(__dirname, '../'), // 入口 entry: { app: './src/main.js' }, // 输出 output: { // 路径 这里是根目录下的dist path: config.build.assetsRoot, // 文件名 filename: '[name].js', publicPath: process.env.NODE_ENV === 'production' // 这里是 /,但要上传到github pages等会路径不对,需要修改为./ ? config.build.assetsPublicPath // 这里配置是 / : config.dev.assetsPublicPath }, // Webpack 在启动后会从配置的入口模块出发找出所有依赖的模块,Resolve 配置 Webpack 如何寻找模块所对应的文件。 resolve: { // 配置了这个,对应的扩展名可以省略 extensions: ['.js', '.vue', '.json'], alias: { // 给定对象的键后的末尾添加 $,以表示精准匹配 node_modules/vue/dist/vue.esm.js // 引用 import Vue from 'vue'就是引入的这个文件最后export default Vue 导出的Vue; // 所以这句可以以任意大写字母命名 比如:import V from 'vue' 'vue$': 'vue/dist/vue.esm.js', // src别名 比如 :引入import HelloWorld from '@/components/HelloWorld' '@': resolve('src'), } }, // 定义一些文件的转换规则 module: { rules: [ // 是否使用eslint 这里配置是true ...(config.dev.useEslint ? [createLintingRule()] : []), { test: /\.vue$/, // vue-loader中文文档:https://vue-loader-v14.vuejs.org/zh-cn/ loader: 'vue-loader', options: vueLoaderConfig }, { // js文件使用babel-loader转换 test: /\.js$/, loader: 'babel-loader', include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')] }, { // 图片文件使用url-loader转换 test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, loader: 'url-loader', options: { // 限制大小10000B(bytes)以内,转成base64编码的dataURL字符串 limit: 10000, // 输出路径 img/名称.7位hash.扩展名 name: utils.assetsPath('img/[name].[hash:7].[ext]') } }, { // 视频文件使用url-loader转换 test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, loader: 'url-loader', options: { limit: 10000, name: utils.assetsPath('media/[name].[hash:7].[ext]') } }, { test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, loader: 'url-loader', options: { limit: 10000, name: utils.assetsPath('fonts/[name].[hash:7].[ext]') } } ] }, // 这里的node是一个对象,其中每个属性都是 Node.js 全局变量或模块的名称,每个 value 是以下其中之一 // empty 提供空对象。 // false 什么都不提供。 // 更多查看 中文文档:https://webpack.docschina.org/configuration/node/ node: { // prevent webpack from injecting useless setImmediate polyfill because Vue // source contains it (although only uses it if it's native). // 防止webpack注入一些polyfill 因为Vue已经包含了这些。 setImmediate: false, // prevent webpack from injecting mocks to Node native modules // that does not make sense for the client dgram: 'empty', fs: 'empty', net: 'empty', tls: 'empty', child_process: 'empty' } }
build/vue-loader.conf.js
vue-loader
配置文件
上文build/webpack.dev.conf.js
提到引入了build/vue-loader.conf.js
。
这个文件主要导出了一份Vue-loader
的配置,
主要有:loaders
,cssSourceMap
,cacheBusting
,transformToRequire
。
具体看该文件注释:
'use strict'const utils = require('./utils')const config = require('../config')const isProduction = process.env.NODE_ENV === 'production'const sourceMapEnabled = isProduction // 这里是true ? config.build.productionSourceMap // 这里是true : config.dev.cssSourceMap// 更多配置 可以查看vue-loader中文文档:https://vue-loader-v14.vuejs.org/zh-cn/module.exports = { // cssLoaders 生成相应loader配置,具体看utils文件中的cssLoader loaders: utils.cssLoaders({ // 是否开启sourceMap,便于调试 sourceMap: sourceMapEnabled, // 是否提取vue单文件的css extract: isProduction }), // 是否开启cssSourceMap,便于调试 cssSourceMap: sourceMapEnabled, // 这里是true // 缓存破坏,进行sourceMap debug时,设置成false很有帮助。 cacheBusting: config.dev.cacheBusting, // vue单文件中,在模板中的图片等资源引用转成require的形式。以便目标资源可以由 webpack 处理。 transformToRequire: { video: ['src', 'poster'], source: 'src', img: 'src', // 默认配置会转换 <img> 标签上的 src 属性和 SVG 的 <image> 标签上的 xlink:href 属性。 image: 'xlink:href' } }
看完了这些文件相应配置,开发环境的相关配置就串起来了。其中config/
文件夹下的配置,笔者都已经注释在build/
文件夹下的对应的文件中,所以就不单独说明了。
那回过头来看,package.json
的scripts
中的npm run build
配置,node build/build.js
,其实就是用node
去执行build/build.js
文件。
build/build.js
npm run build
指定执行的文件
这个文件主要做了以下几件事情:
1、引入build/check-versions
文件,检查node
和npm
的版本,
2、引入相关插件和配置,其中引入了webpack
生产环境的配置build/webpack.prod.conf.js
,
3、先控制台输出loading
,删除dist
目录下的文件,开始构建,构建失败和构建成功都给出相应的提示信息。
具体可以查看相应的注释:
'use strict'// 检查node npm的版本require('./check-versions')() process.env.NODE_ENV = 'production'// 命令行中的loadingconst ora = require('ora')// 删除文件或文件夹const rm = require('rimraf')// 路径相关const path = require('path')// 控制台输入样式 chalk 更多查看:https://github.com/chalk/chalkconst chalk = require('chalk')// 引入webpackconst webpack = require('webpack')// 引入config/index.jsconst config = require('../config')// 引入 生产环境webpack配置const webpackConfig = require('./webpack.prod.conf')// 控制台输入开始构建loadingconst spinner = ora('building for production...') spinner.start()// 删除原有构建输出的目录文件 这里是dist 和 staticrm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => { // 如果出错,抛出错误 if (err) throw err webpack(webpackConfig, (err, stats) => { // 关闭 控制台输入开始构建loading spinner.stop() // 如果出错,抛出错误 if (err) throw err process.stdout.write(stats.toString({ colors: true, modules: false, children: false, // If you are using ts-loader, setting this to true will make TypeScript errors show up during build. chunks: false, chunkModules: false }) + '\n\n') // 如果有错,控制台输出构建失败 if (stats.hasErrors()) { console.log(chalk.red(' Build failed with errors.\n')) process.exit(1) } // 控制台输出构建成功相关信息 console.log(chalk.cyan(' Build complete.\n')) console.log(chalk.yellow( ' Tip: built files are meant to be served over an HTTP server.\n' + ' Opening index.html over file:// won\'t work.\n' )) }) })
build/check-versions
检查node
和npm
版本
上文提到build/check-versions
检查node
和npm
版本,这个文件主要引入了一些插件和配置,最后导出一个函数,版本不符合预期就输出警告。
具体查看这个配置文件注释:
'use strict'// 控制台输入样式 chalk 更多查看:https://github.com/chalk/chalkconst chalk = require('chalk')// 语义化控制版本的插件 更多查看:https://github.com/npm/node-semverconst semver = require('semver')// package.json配置const packageConfig = require('../package.json')// shell 脚本 Unix shell commands for Node.js 更多查看:https://github.com/shelljs/shelljsconst shell = require('shelljs')function exec (cmd) { return require('child_process').execSync(cmd).toString().trim() }const versionRequirements = [ { name: 'node', currentVersion: semver.clean(process.version), // 这里配置是"node": ">= 6.0.0", versionRequirement: packageConfig.engines.node } ]// 需要使用npmif (shell.which('npm')) { versionRequirements.push({ name: 'npm', currentVersion: exec('npm --version'), // 这里配置是"npm": ">= 3.0.0" versionRequirement: packageConfig.engines.npm }) }// 导出一个检查版本的函数module.exports = function () { const warnings = [] for (let i = 0; i < versionRequirements.length; i++) { const mod = versionRequirements[i] // 当前版本不大于所需版本 if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) { warnings.push(mod.name + ': ' + chalk.red(mod.currentVersion) + ' should be ' + chalk.green(mod.versionRequirement) ) } } // 如果有警告,全部输出到控制台 if (warnings.length) { console.log('') console.log(chalk.yellow('To use this template, you must update following to modules:')) console.log() for (let i = 0; i < warnings.length; i++) { const warning = warnings[i] console.log(' ' + warning) } console.log() process.exit(1) } }
build/webpack.prod.conf.js
webpack
生产环境配置
上文build/build.js
提到,引入了这个配置文件。
这个文件主要做了以下几件事情:
1、引入一些插件和配置,其中引入了build/webpack.base.conf.js
webpack
基本配置文件,
2、用DefinePlugin
定义环境,
3、合并基本配置,定义自己的配置webpackConfig
,配置了一些modules
下的rules
,devtools
配置,output
输出配置,一些处理js
、提取css
、压缩css
、输出html
插件、提取公共代码等的plugins
,
4、如果启用gzip
,再使用相应的插件处理,
5、如果启用了分析打包后的插件,则用webpack-bundle-analyzer
,
6、最后导出这份配置。
具体可以查看这个文件配置注释:
'use strict'// 引入node路径相关const path = require('path')// 引入utils工具函数const utils = require('./utils')// 引入webpackconst webpack = require('webpack')// 引入config/index.js配置文件const config = require('../config')// 合并webpack配置的插件const merge = require('webpack-merge')// 基本的webpack配置const baseWebpackConfig = require('./webpack.base.conf')// 拷贝文件和文件夹的插件const CopyWebpackPlugin = require('copy-webpack-plugin')// 压缩处理HTML的插件const HtmlWebpackPlugin = require('html-webpack-plugin')const ExtractTextPlugin = require('extract-text-webpack-plugin')// 压缩处理css的插件const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')// 压缩处理js的插件const UglifyJsPlugin = require('uglifyjs-webpack-plugin')// 用DefinePlugin定义环境const env = process.env.NODE_ENV === 'testing' // 这里是 { NODE_ENV: '"testing"' } ? require('../config/test.env') // 这里是 { NODE_ENV: '"production"' } : require('../config/prod.env')// 合并基本webpack配置const webpackConfig = merge(baseWebpackConfig, { module: { // 通过styleLoaders函数生成样式的一些规则 rules: utils.styleLoaders({ // sourceMap这里是true sourceMap: config.build.productionSourceMap, // 是否提取css到单独的css文件 extract: true, // 是否使用postcss usePostCSS: true }) }, // 配置使用sourceMap true 这里是 #source-map devtool: config.build.productionSourceMap ? config.build.devtool : false, output: { // 这里是根目录下的dist path: config.build.assetsRoot, // 文件名称 chunkhash filename: utils.assetsPath('js/[name].[chunkhash].js'), // chunks名称 chunkhash chunkFilename: utils.assetsPath('js/[id].[chunkhash].js') }, plugins: [ // http://vuejs.github.io/vue-loader/en/workflow/production.html // 定义具体是什么环境 new webpack.DefinePlugin({ 'process.env': env }), // 压缩js插件 new UglifyJsPlugin({ uglifyOptions: { compress: { // 警告 warnings: false // 构建后的文件 常用的配置还有这些 // 去除console.log 默认为false。 传入true会丢弃对console函数的调用。 // drop_console: true, // 去除debugger // drop_debugger: true, // 默认为null. 你可以传入一个名称的数组,而UglifyJs将会假定那些函数不会产生副作用。 // pure_funcs: [ 'console.log', 'console.log.apply' ], } }, // 是否开启sourceMap 这里是true sourceMap: config.build.productionSourceMap, // 平行处理(同时处理)加快速度 parallel: true }), // extract css into its own file // 提取css到单独的css文件 new ExtractTextPlugin({ // 提取到相应的文件名 使用内容hash contenthash filename: utils.assetsPath('css/[name].[contenthash].css'), // Setting the following option to `false` will not extract CSS from codesplit chunks. // Their CSS will instead be inserted dynamically with style-loader when the codesplit chunk has been loaded by webpack. // It's currently set to `true` because we are seeing that sourcemaps are included in the codesplit bundle as well when it's `false`, // increasing file size: https://github.com/vuejs-templates/webpack/issues/1110 // allChunks 默认是false,true指提取所有chunks包括动态引入的组件。 allChunks: true, }), // Compress extracted CSS. We are using this plugin so that possible // duplicated CSS from different components can be deduped. new OptimizeCSSPlugin({ // 这里配置是true cssProcessorOptions: config.build.productionSourceMap ? { safe: true, map: { inline: false } } : { safe: true } }), // generate dist index.html with correct asset hash for caching. // you can customize output by editing /index.html // see https://github.com/ampedandwired/html-webpack-plugin new HtmlWebpackPlugin({ // 输出html名称 filename: process.env.NODE_ENV === 'testing' ? 'index.html' // 这里是 根目录下的dist/index.html : config.build.index, // 使用哪个模板 template: 'index.html', // inject 默认值 true,script标签位于html文件的 body 底部 // body 通true, header, script 标签位于 head 标签内 // false 不插入生成的 js 文件,只是单纯的生成一个 html 文件 inject: true, // 压缩 minify: { // 删除注释 removeComments: true, // 删除空格和换行 collapseWhitespace: true, // 删除html标签中属性的双引号 removeAttributeQuotes: true // 更多配置查看html-minifier插件 // more options: // https://github.com/kangax/html-minifier#options-quick-reference }, // necessary to consistently work with multiple chunks via CommonsChunkPlugin // 在chunk被插入到html之前,你可以控制它们的排序。允许的值 ‘none’ | ‘auto’ | ‘dependency’ | {function} 默认为‘auto’. // dependency 依赖(从属) chunksSortMode: 'dependency' }), // keep module.id stable when vendor modules does not change // 根据代码内容生成普通模块的id,确保源码不变,moduleID不变。 new webpack.HashedModuleIdsPlugin(), // enable scope hoisting // 开启作用域提升 webpack3新的特性,作用是让代码文件更小、运行的更快 new webpack.optimize.ModuleConcatenationPlugin(), // split vendor js into its own file // 提取公共代码 new webpack.optimize.CommonsChunkPlugin({ name: 'vendor', minChunks (module) { // any required modules inside node_modules are extracted to vendor return ( module.resource && /\.js$/.test(module.resource) && module.resource.indexOf( path.join(__dirname, '../node_modules') ) === 0 ) } }), // extract webpack runtime and module manifest to its own file in order to // prevent vendor hash from being updated whenever app bundle is updated // 提取公共代码 new webpack.optimize.CommonsChunkPlugin({ // 把公共的部分放到 manifest 中 name: 'manifest', // 传入 `Infinity` 会马上生成 公共chunk,但里面没有模块。 minChunks: Infinity }), // This instance extracts shared chunks from code splitted chunks and bundles them // in a separate chunk, similar to the vendor chunk // see: https://webpack.js.org/plugins/commons-chunk-plugin/#extra-async-commons-chunk // 提取动态组件 new webpack.optimize.CommonsChunkPlugin({ name: 'app', // 如果设置为 `true`,一个异步的 公共chunk 会作为 `options.name` 的子模块,和 `options.chunks` 的兄弟模块被创建。 // 它会与 `options.chunks` 并行被加载。可以通过提供想要的字符串,而不是 `true` 来对输出的文件进行更换名称。 async: 'vendor-async', // 如果设置为 `true`,所有 公共chunk 的子模块都会被选择 children: true, // 最小3个,包含3,chunk的时候提取 minChunks: 3 }), // copy custom static assets // 把static资源复制到相应目录。 new CopyWebpackPlugin([ { from: path.resolve(__dirname, '../static'), // 这里配置是static to: config.build.assetsSubDirectory, // 忽略.开头的文件。比如这里的.gitkeep,这个文件是指空文件夹也提交到git ignore: ['.*'] } ]) ] })// 如果开始gzip压缩,使用compression-webpack-plugin插件处理。这里配置是false// 需要使用是需要安装 npm i compression-webpack-plugin -Dif (config.build.productionGzip) { const CompressionWebpackPlugin = require('compression-webpack-plugin') webpackConfig.plugins.push( new CompressionWebpackPlugin({ // asset: 目标资源名称。 [file] 会被替换成原始资源。 // [path] 会被替换成原始资源的路径, [query] 会被替换成查询字符串。默认值是 "[path].gz[query]"。 asset: '[path].gz[query]', // algorithm: 可以是 function(buf, callback) 或者字符串。对于字符串来说依照 zlib 的算法(或者 zopfli 的算法)。默认值是 "gzip"。 algorithm: 'gzip', // test: 所有匹配该正则的资源都会被处理。默认值是全部资源。 // config.build.productionGzipExtensions 这里是['js', 'css'] test: new RegExp( '\\.(' + config.build.productionGzipExtensions.join('|') + ')$' ), // threshold: 只有大小大于该值的资源会被处理。单位是 bytes。默认值是 0。 threshold: 10240, // minRatio: 只有压缩率小于这个值的资源才会被处理。默认值是 0.8。 minRatio: 0.8 }) ) }// 输出分析的插件 运行npm run build --report// config.build.bundleAnalyzerReport这里是 process.env.npm_config_report// build结束后会自定打开 http://127.0.0.1:8888 链接if (config.build.bundleAnalyzerReport) { // 更多查看链接地址:https://www.npmjs.com/package/webpack-bundle-analyzer const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin webpackConfig.plugins.push(new BundleAnalyzerPlugin()) }// 当然也可以用官方提供的网站 http://webpack.github.io/analyse/#home// 运行类似 webpack --profile --json > stats.json 命令// 把生成的构建信息stats.json上传即可// 最终导出 webpackConfigmodule.exports = webpackConfig
至此,我们就分析完了package.json
中的npm run dev
和npm run build
两个命令。测试相关的类似就略过吧。
npm run lint
,.eslintrc.js
中的配置不多,更多可以查看eslint英文文档或eslint
中文官网,所以也略过吧。不过提一下,把eslint
整合到git
工作流。可以安装husky
,npm i husky -S
。安装后,配置package.json
的scripts
中,配置precommit
,具体如下:
"scripts": { "lint": "eslint --ext .js,.vue src test/unit test/e2e/specs", "precommit": "npm run lint", },
配置好后,每次git commit -m
提交会检查代码是否通过eslint
校验,如果没有校验通过则提交失败。还可以配置prepush
。husky
不断在更新,现在可能与原先的配置不太相同了,具体查看husky github仓库。原理就是git-hooks
,pre-commit
的钩子。对shell
脚本熟悉的同学也可以自己写一份pre-commit
。复制到项目的.git/hooks/pre-commit
中。不需要依赖husky
包。我司就是用的shell
脚本。
最后提一下.babelrc
文件中的配置。
.babelrc
babel
相关配置
配置了一些转码规则。这里附上两个链接:babel
英文官网和babel
的中文官网。
具体看文件中的配置注释:
{ // presets指明转码的规则 "presets": [ // env项是借助插件babel-preset-env,下面这个配置说的是babel对es6,es7,es8进行转码,并且设置amd,commonjs这样的模块化文件,不进行转码 ["env", { "modules": false, "targets": { "browsers": ["> 1%", "last 2 versions", "not ie <= 8"] } }], "stage-2" ], // plugins 属性告诉 Babel 要使用哪些插件,插件可以控制如何转换代码。 // transform-vue-jsx 表明可以在项目中使用jsx语法,会使用这个插件转换 "plugins": ["transform-vue-jsx", "transform-runtime"], // 在特定的环境中所执行的转码规则,当环境变量是下面的test就会覆盖上面的设置 "env": { // test 是提前设置的环境变量,如果没有设置BABEL_ENV则使用NODE_ENV,如果都没有设置默认就是development "test": { "presets": ["env", "stage-2"], "plugins": ["transform-vue-jsx", "transform-es2015-modules-commonjs", "dynamic-import-node"] } } }
文件中presets
中有配置env
和stage-2
,可能不知道是什么。这里引用深入浅出webpack书中,第三章,3-1
使用ES6
语言 小节的一段,解释一下。
presets
属性告诉Babel
要转换的源码使用了哪些新的语法特性,一个 Presets 对一组新语法特性提供支持,多个Presets
可以叠加。Presets
其实是一组Plugins
的集合,每一个Plugin
完成一个新语法的转换工作。Presets
是按照ECMAScript
草案来组织的,通常可以分为以下三大类(书中就是说三大类,我发现就两点~~~):
1、已经被写入 ECMAScript 标准里的特性,由于之前每年都有新特性被加入到标准里,所以又可细分为:
es2015 包含在2015里加入的新特性;
es2016 包含在2016里加入的新特性;
es2017 包含在2017里加入的新特性;
es2017 包含在2017里加入的新特性;
env 包含当前所有 ECMAScript 标准里的最新特性。
2、被社区提出来的但还未被写入ECMAScript
标准里特性,这其中又分为以下四种:stage0
只是一个美好激进的想法,有Babel
插件实现了对这些特性的支持,但是不确定是否会被定为标准;stage1
值得被纳入标准的特性;stage2
该特性规范已经被起草,将会被纳入标准里;stage3
该特性规范已经定稿,各大浏览器厂商和 `` 社区开始着手实现;stage4
在接下来的一年将会加入到标准里去。
至此,就算相对完整的分析完了Vue-cli
(版本v2.9.3
)搭建的webpack
项目工程。希望对大家有所帮助。
小结
1、分析这些,逐行注释,还是需要一些时间的。其中有些不是很明白的地方,及时查阅相应的官方文档和插件文档(建议看英文文档和最新的文档),不过文档没写明白的地方,可以多搜索一些别人的博客文章,相对比较清晰明了。
2、前端发展太快,这个Vue-cli@2.9.3
webpack
版本还是v3.x
,webpack
现在官方版本已经是v4.12.0
,相信不久后,Vue-cli
也将发布支持webpack v4.x
的版本,v3.0.0
已经是beta.16
了。
3、后续有余力,可能会继续分析新版的vue-cli
构建的webpack
项目工程。
作者:轩辕Rowboat
链接:https://www.jianshu.com/p/0579b8dff202