前言

已经有很多分析Vue-cli搭建工程的文章,为什么自己还要写一遍呢。学习就好比是座大山,人们沿着不同的路登山,分享着自己看到的风景。你不一定能看到别人看到的风景,体会到别人的心情。只有自己去登山,才能看到不一样的风景,体会才更加深刻。

项目放在笔者的github上,分析vue-cli@2.9.3 搭建的webpack项目工程。方便大家克隆下载,或者在线查看。同时也求个star ^_^,也是对笔者的一种鼓励和支持。

正文从这里开始~

使用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 init
vue 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-serverwebpack-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、配置开发环境一些devServerplugin等配置,
4、最后导出了一个Promise,根据配置的端口,寻找可用的端口来启动服务。

具体可以看build/webpack.dev.conf.js这个文件注释:

'use strict'
// 引入工具函数
const utils = require('./utils')
// 引入webpack
const 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-plugin
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
// 查找可用端口 // github仓库 https://github.com/indexzero/node-portfinder
const 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():将一个回调函数放在下次事件循环的顶部。// host
const HOST = process.env.HOST
// 端口
const PORT = process.env.PORT && Number(process.env.PORT)// 合并基本的webpack配置
const devWebpackConfig = merge(baseWebpackConfig, {module: {// cssSourceMap这里配置的是truerules: 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/#developmentdevtool: config.dev.devtool,// these devServer options should be customized in /config/index.jsdevServer: {// 配置在客户端的日志等级,这会影响到你在浏览器开发者工具控制台里看到的日志内容。// 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// 这里配置的是localhosthost: HOST || config.dev.host,// 端口号 这里配置的是8080port: PORT || config.dev.port,// 打开浏览器,这里配置是不打开falseopen: config.dev.autoOpenBrowser,// 是否在浏览器以遮罩形式显示报错信息 这里配置的是trueoverlay: 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:8080quiet: true, // necessary for FriendlyErrorsPlugin// webpack-dev-middleware// watch: false,// 启用 Watch 模式。这意味着在初始构建之后,webpack 将继续监听任何已解析文件的更改。Watch 模式默认关闭。// webpack-dev-server 和 webpack-dev-middleware 里 Watch 模式默认开启。// Watch 模式的选项watchOptions: {// 或者指定毫秒为单位进行轮询。// 这里配置为falsepoll: 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-pluginnew 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([{// 这里是 staticfrom: path.resolve(__dirname, '../static'),// 这里是 staticto: config.dev.assetsSubDirectory,// 忽略.开头的文件。比如这里的.gitkeep,这个文件是指空文件夹也提交到gitignore: ['.*']}])]
})
// 导出一个promise
module.exports = new Promise((resolve, reject) => {// process.env.PORT 可以在命令行指定端口号,比如PORT=2000 npm run dev,那访问就是http://localhost:2000// config.dev.port 这里配置是 8080portfinder.basePort = process.env.PORT || config.dev.port// 以配置的端口为基准,寻找可用的端口,比如:如果8080占用,那就8081,以此类推// github仓库 https://github.com/indexzero/node-portfinderportfinder.getPort((err, port) => {if (err) {reject(err)} else {// publish the new Port, necessary for e2e testsprocess.env.PORT = port// add port to devServer configdevWebpackConfig.devServer.port = port// Add FriendlyErrorsPlugindevWebpackConfig.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.js
const 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_posixreturn 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,来决定是否采用postcssLoaderconst loaders = options.usePostCSS ? [cssLoader, postcssLoader] : [cssLoader]if (loader) {loaders.push({loader: loader + '-loader',// 合并 loaderOptions 生成optionsoptions: 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.htmlreturn {css: generateLoaders(),postcss: generateLoaders(),less: generateLoaders('less'),// sass indentedSyntax 语法缩进,类似下方格式// #main//   color: blue//   font-size: 0.3emsass: 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') returnconst 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,输出outputresolvemodule下的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配置是falseemitWarning: !config.dev.showEslintErrorsInOverlay}
})module.exports = {// 运行环境的上下文,就是实际的目录,也就是项目根目录context: path.resolve(__dirname, '../'),// 入口entry: {app: './src/main.js'},// 输出output: {// 路径 这里是根目录下的distpath: 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 clientdgram: '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的配置, 主要有:loaderscssSourceMapcacheBustingtransformToRequire

具体看该文件注释:

'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文件中的cssLoaderloaders: utils.cssLoaders({// 是否开启sourceMap,便于调试sourceMap: sourceMapEnabled,// 是否提取vue单文件的cssextract: 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.jsonscripts中的npm run build配置,node build/build.js,其实就是用node去执行build/build.js文件。

build/build.js npm run build 指定执行的文件

这个文件主要做了以下几件事情:
1、引入build/check-versions文件,检查nodenpm的版本,
2、引入相关插件和配置,其中引入了webpack生产环境的配置build/webpack.prod.conf.js
3、先控制台输出loading,删除dist目录下的文件,开始构建,构建失败和构建成功都给出相应的提示信息。

具体可以查看相应的注释:

'use strict'
// 检查node npm的版本
require('./check-versions')()process.env.NODE_ENV = 'production'
// 命令行中的loading
const ora = require('ora')
// 删除文件或文件夹
const rm = require('rimraf')
// 路径相关
const path = require('path')
// 控制台输入样式 chalk 更多查看:https://github.com/chalk/chalk
const chalk = require('chalk')
// 引入webpack
const webpack = require('webpack')
// 引入config/index.js
const config = require('../config')
// 引入 生产环境webpack配置
const webpackConfig = require('./webpack.prod.conf')// 控制台输入开始构建loading
const spinner = ora('building for production...')
spinner.start()// 删除原有构建输出的目录文件 这里是dist 和 static
rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => {// 如果出错,抛出错误if (err) throw errwebpack(webpackConfig, (err, stats) => {// 关闭 控制台输入开始构建loadingspinner.stop()// 如果出错,抛出错误if (err) throw errprocess.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 检查nodenpm版本

上文提到build/check-versions 检查nodenpm版本,这个文件主要引入了一些插件和配置,最后导出一个函数,版本不符合预期就输出警告。

具体查看这个配置文件注释:

'use strict'
// 控制台输入样式 chalk 更多查看:https://github.com/chalk/chalk
const chalk = require('chalk')
// 语义化控制版本的插件 更多查看:https://github.com/npm/node-semver
const semver = require('semver')
// package.json配置
const packageConfig = require('../package.json')
// shell 脚本 Unix shell commands for Node.js 更多查看:https://github.com/shelljs/shelljs
const 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}
]
// 需要使用npm
if (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下的rulesdevtools配置,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')
// 引入webpack
const 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这里是truesourceMap: config.build.productionSourceMap,// 是否提取css到单独的css文件extract: true,// 是否使用postcssusePostCSS: true})},// 配置使用sourceMap true 这里是 #source-mapdevtool: config.build.productionSourceMap ? config.build.devtool : false,output: {// 这里是根目录下的distpath: config.build.assetsRoot,// 文件名称 chunkhashfilename: utils.assetsPath('js/[name].[chunkhash].js'),// chunks名称 chunkhashchunkFilename: 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 这里是truesourceMap: config.build.productionSourceMap,// 平行处理(同时处理)加快速度parallel: true}),// extract css into its own file// 提取css到单独的css文件new ExtractTextPlugin({// 提取到相应的文件名 使用内容hash contenthashfilename: 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({// 这里配置是truecssProcessorOptions: 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-pluginnew 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 vendorreturn (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'),// 这里配置是staticto: config.build.assetsSubDirectory,// 忽略.开头的文件。比如这里的.gitkeep,这个文件是指空文件夹也提交到gitignore: ['.*']}])]
})
// 如果开始gzip压缩,使用compression-webpack-plugin插件处理。这里配置是false
// 需要使用是需要安装 npm i compression-webpack-plugin -D
if (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-analyzerconst BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPluginwebpackConfig.plugins.push(new BundleAnalyzerPlugin())
}
// 当然也可以用官方提供的网站 http://webpack.github.io/analyse/#home
// 运行类似 webpack --profile --json > stats.json 命令
// 把生成的构建信息stats.json上传即可// 最终导出 webpackConfig
module.exports = webpackConfig复制代码

至此,我们就分析完了package.json中的npm run devnpm run build两个命令。测试相关的类似就略过吧。

npm run lint.eslintrc.js中的配置不多,更多可以查看eslint英文文档或eslint中文官网,所以也略过吧。不过提一下,把eslint整合到git工作流。可以安装huskynpm i husky -S。安装后,配置package.jsonscripts中,配置precommit,具体如下:

"scripts": {"lint": "eslint --ext .js,.vue src test/unit test/e2e/specs","precommit": "npm run lint",
},
复制代码

配置好后,每次git commit -m提交会检查代码是否通过eslint校验,如果没有校验通过则提交失败。还可以配置prepushhusky不断在更新,现在可能与原先的配置不太相同了,具体查看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中有配置envstage-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项目工程。希望对大家有所帮助。
项目放在笔者的github上,分析vue-cli@2.9.3 搭建的webpack项目工程。方便大家克隆下载,或者在线查看。同时也求个star ^_^,也是对笔者的一种鼓励和支持。
笔者知识能力有限,文章有什么不妥之处,欢迎指出~

小结

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若川为名混迹于江湖。前端路上 | PPT爱好者 | 所知甚少,唯善学。
个人博客
segmentfault前端视野专栏,开通了前端视野专栏,欢迎关注
掘金专栏,欢迎关注
知乎前端视野专栏,开通了前端视野专栏,欢迎关注
github,欢迎follow~

分析vue-cli@2.9.3 搭建的webpack项目工程 1相关推荐

  1. 分析vue-cli@2.9.3 搭建的webpack项目工程

    前言 已经有很多分析Vue-cli搭建工程的文章,为什么自己还要写一遍呢.学习就好比是座大山,人们沿着不同的路登山,分享着自己看到的风景.你不一定能看到别人看到的风景,体会到别人的心情.只有自己去登山 ...

  2. vue-cli@2.9.3 搭建的webpack项目工程

    前言 已经有很多分析 Vue-cli搭建工程的文章,为什么自己还要写一遍呢.学习就好比是座大山,人们沿着不同的路登山,分享着自己看到的风景.你不一定能看到别人看到的风景,体会到别人的心情.只有自己去登 ...

  3. Vue + Element + VSCode 零基础搭建第一个项目

    一.确认node.npm的是否安装. 在键盘按下[win+R]键,输入cmd,然后回车,打开cmd窗口,在cmd窗口内分别使用以下两个指令检查node.npm的安装版本(输入后回车执行) node - ...

  4. Vue 单文件组件||Vue 单文件组件的基本用法||webpack 中配置 vue 组件的加载器|| 在 webpack 项目中使用 vue

    Vue 单文件组件 传统组件的问题和解决方案 1. 问题 1. 全局定义的组件必须保证组件的名称不重复 2. 字符串模板缺乏语法高亮,在 HTML 有多行的时候,需要用到丑陋的 \ 3. 不支持 CS ...

  5. 【Vue 3 实战一】搭建一个新项目并上传至gitee

    提示:专栏内容均为原创,搬运必究 文章目录 一.Vue3的新特性? 二.创建新项目 1. 利用脚手架 2.项目配置选择 (建议与下方一致) 三.运行项目 1. 安装依赖 2. 运行项目 3. 上传代码 ...

  6. 从零搭建react + webpack项目

    初始化项目 mkdir my-react cd my-react npm init 安装 webpack npm install webpack webpack-cli --save-dev 最简单的 ...

  7. React 环境搭建以及创建项目工程(一)

    下载软件 需要用到三个软件 node.js https://nodejs.org/zh-cn/ vs code. https://code.visualstudio.com/ google 浏览器 h ...

  8. React 环境搭建以及创建项目工程(二)

    创建工程 首先创建一个工程 npx create-react-app weibo cd移动到当前创建的文件下 cd weibo 安装 React 路由 npm install react-router ...

  9. 做了两年前端开发,平时就是拿 Vue 写写页面和组件,简历的项目经历应该怎么写得好看?

    无意间在知乎看见的,感觉讲得很有道理.看原文点这里  ------>  做了两年前端开发,平时就是拿 Vue 写写页面和组件,简历的项目经历应该怎么写得好看? 2年经验做的东西没什么技术含量,应 ...

  10. Vue CLI 3 多页应用项目的搭建

    在项目初期时,从零开始搭建和配置本地前端开发环境是一项很繁琐的工作,需要考虑到项目目录结构.项目基本配置.Webpack 配置等等.通过 Vue CLI 3 可以快速的生成一个项目,这样我们就可以专注 ...

最新文章

  1. QLocalServer与QLocalSocket进程通讯
  2. Android中设置组件多进程
  3. nginx优化-nginx事件处理模型优化use epoll;
  4. import() 动态加载component组件失败
  5. c++中new的总结(动态管理,malloc存在的问题,malloc与new的区别)
  6. 前端学习(2040)vue之电商管理系统电商系统之执行build命令所有的警告
  7. .魔术方法、魔术常量
  8. 网易考拉Android客户端路由总线设计
  9. Spring MVC 学习笔记(整理)
  10. javaweb中服务器启动所执行的程序
  11. SQL语句统计每天、每月、每年、今天、昨天、本周、上周的数据
  12. python 路由追踪_tracert跟踪路由
  13. 和导师闹僵跑来实习?拼了命也要拿到大厂实习offer
  14. 远程桌面3389加固
  15. hive 计算周几_HIVE如何返回周or周几
  16. 前端开发不得不知道的网站大全
  17. Python基础——类属性、类方法、异常
  18. 大学计算机一级b类和c类有何区别,申论(C类)和A类,B类有什么区别吗
  19. sqlserver阻止保存要求重新创建表的更改
  20. Android双屏异显

热门文章

  1. LRUCache和FastLRUCache实现分析
  2. nginx+php简单配置环境
  3. extThree20XML extThree20JSON 引入到工程中的方式
  4. 可逆加密,支持中文,支持密钥,加密后的密文每次都不同,且可随意改变算法 组件之Vb.Net版本...
  5. 第十三章 确定性策略梯度(Deterministic Policy Gradient Algorithms,DPG)-强化学习理论学习与代码实现(强化学习导论第二版)
  6. 来了来了!github开源作业车间调度平台正式上线!
  7. 【python】多线程小结
  8. 【笔记】VUE学习笔记
  9. day11--Matplotlib--柱形图与盒图
  10. python socket模块 和pyqt5_PyQt5+Socket实现CS架构的建议聊天室