[toc]
目录
结构预览
├─build // 保存一些 webpack 的初始化配置, 项目构建
│ ├─build.js // 生产环境构建
│ ├─check-version.js // 检查 npm、node 版本
│ ├─vue-loader.conf.js // webpack loader 配置
│ ├─webpack.base.conf.js// webpack 基础配置
│ ├─webpack.dev.conf.js // 开发环境配置,构建本地开发服务器
│ ├─webpack.prod.conf.js// 生产环境的配置
│
├─config // config 文件夹保存一些项目初始化的配置
│ ├─dev.env.js // 开发环境的配置
│ ├─index.js // 项目一些配置变量
│ ├─prod.env.js // 生产环境的配置
│
├─dist // 打包后的项目
├─node_modules // 依赖包
│
├─src // 源码目录
│ ├─assets // 静态文件目录
│ ├─components // 组件文件
│ ├─router // 路由
│ ├─App.vue // 是项目入口文件
│ ├─main.js // 是项目的核心文件,入口
├─static // 静态资源目录
├─.babelrc // Babel 的配置文件
├─.editorconfig // 代码规范配置文件
├─.gitignore // git 忽略配置文件
├─.postcssrc.js // postcss 插件配置文件
├─index.html // 页面入口文件
├─package-lock.json // 项目包管控文件
├─package.json // 项目配置
└─README.md // 项目说明书
结构解析
build
dev-server.js
首先来看执行”npm run dev”时候最先执行的 build/dev-server.js 文件。该文件主要完成下面几件事情:
检查 node 和 npm 的版本、引入相关插件和配置
webpack 对源码进行编译打包并返回 compiler 对象
创建 express 服务器
配置开发中间件(webpack-dev-middleware)和 + 热重载中间件(webpack-hot-middleware)
挂载代理服务和中间件
配置静态资源
启动服务器监听特定端口(8080)
自动打开浏览器并打开特定网址(localhost:8080)
说明:express 服务器提供静态文件服务,不过它还使用了 http-proxy-middleware,一个 http 请求代理的中间件。前端开发过程中需要使用到后台的 API 的话,可以通过配置 proxyTable 来将相应的后台请求代理到专用的 API 服务器。
// 检查 NodeJS 和 npm 的版本
require(‘./check-versions’)()
// 获取基本配置
var config = require(‘../config’)
// 如果 Node 的环境变量中没有设置当前的环境(NODE_ENV),则使用 config 中的 dev 环境配置作为当前的环境
if (!process.env.NODE_ENV) {
process.env.NODE_ENV = JSON.parse(config.dev.env.NODE_ENV)
}
// opn 是一个可以调用默认软件打开网址、图片、文件等内容的插件
// 这里用它来调用默认浏览器打开 dev-server 监听的端口,例如:localhost:8080
var opn = require(‘opn’)
var path = require(‘path’)
var express = require(‘express’)
var webpack = require(‘webpack’)
// http-proxy-middleware 是一个 express 中间件,用于将 http 请求代理到其他服务器
// 例:localhost:8080/api/xxx –> localhost:3000/api/xxx
// 这里使用该插件可以将前端开发中涉及到的请求代理到提供服务的后台服务器上,方便与服务器对接
var proxyMiddleware = require(‘http-proxy-middleware’)
// 开发环境下的 webpack 配置
var webpackConfig = require(‘./webpack.dev.conf’)
// dev-server 监听的端口,如果没有在命令行传入端口号,则使用 config.dev.port 设置的端口,例如 8080
var port = process.env.PORT || config.dev.port
// 用于判断是否要自动打开浏览器的布尔变量,当配置文件中没有设置自动打开浏览器的时候其值为 false
var autoOpenBrowser = !!config.dev.autoOpenBrowser
// HTTP 代理表,指定规则,将某些 API 请求代理到相应的服务器
var proxyTable = config.dev.proxyTable
// 创建 express 服务器
var app = express()
// webpack 根据配置开始编译打包源码并返回 compiler 对象
var compiler = webpack(webpackConfig)
// webpack-dev-middleware 将 webpack 编译打包后得到的产品文件存放在内存中而没有写进磁盘
// 将这个中间件挂到 express 上使用之后即可提供这些编译后的产品文件服务
var devMiddleware = require(‘webpack-dev-middleware’)(compiler, {
publicPath: webpackConfig.output.publicPath, // 设置访问路径为 webpack 配置中的 output 里面所对应的路径
quiet: true // 设置为 true,使其不要在控制台输出日志
})
// webpack-hot-middleware,用于实现热重载功能的中间件
var hotMiddleware = require(‘webpack-hot-middleware’)(compiler, {
log: false, // 关闭控制台的日志输出
heartbeat: 2000 // 发送心跳包的频率
})
// webpack(重新)编译打包完成后并将 js、css 等文件 inject 到 html 文件之后,通过热重载中间件强制页面刷新
compiler.plugin(‘compilation’, function (compilation) {
compilation.plugin(‘html-webpack-plugin-after-emit’, function (data, cb) {
hotMiddleware.publish({action: ‘reload’})
cb()
})
})
// 根据 proxyTable 中的代理请求配置来设置 express 服务器的 http 代理规则
Object.keys(proxyTable).forEach(function (context) {
var options = proxyTable[context]
// 格式化 options,例如将 ’www.example.com’ 变成{target: ‘www.example.com’}
if (typeof options === ‘string’) {
options = {target: options}
}
app.use(proxyMiddleware(options.filter || context, options))
})
// handle fallback for HTML5 history API
// 重定向不存在的 URL,用于支持 SPA(单页应用)
// 例如使用 vue-router 并开启了 history 模式
app.use(require(‘connect-history-api-fallback’)())
// serve webpack bundle output
// 挂载 webpack-dev-middleware 中间件,提供 webpack 编译打包后的产品文件服务
app.use(devMiddleware)
// enable hot-reload and state-preserving
// compilation error display
// 挂载热重载中间件
app.use(hotMiddleware)
// serve pure static assets
// 提供 static 文件夹上的静态文件服务
var staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory)
app.use(staticPath, express.static(‘./static’))
// 访问链接
var uri = ‘http://localhost:’ + port
// 创建 promise,在应用服务启动之后 resolve
// 便于外部文件 require 了这个 dev-server 之后的代码编写
var _resolve
var readyPromise = new Promise(resolve => {
_resolve = resolve
})
console.log(‘> Starting dev server…’)
// webpack-dev-middleware 等待 webpack 完成所有编译打包之后输出提示语到控制台,表明服务正式启动
// 服务正式启动才自动打开浏览器进入页面
devMiddleware.waitUntilValid(() => {
console.log(‘> Listening at ‘ + uri + ‘\n’)
// when env is testing, don’t need open it
if (autoOpenBrowser && process.env.NODE_ENV !== ‘testing’) {
opn(uri)
}
_resolve()
})
// 启动 express 服务器并监听相应的端口
var server = app.listen(port)
// 暴露本模块的功能给外部使用,例如下面这种用法
// var devServer = require(‘./build/dev-server’)
// devServer.ready.then(() => {…})
// if (…) {devServer.close() }
module.exports = {
ready: readyPromise,
close: () => {
server.close()
}
}
webpack.base.conf.js
从代码中看到,dev-server 使用的 webpack 配置来自 build/webpack.dev.conf.js 文件(测试环境下使用的是 build/webpack.prod.conf.js,这里暂时不考虑测试环境)。而 build/webpack.dev.conf.js 中又引用了 webpack.base.conf.js,所以这里我先分析 webpack.base.conf.js。
webpack.base.conf.js 主要完成了下面这些事情:
配置 webpack 编译入口
配置 webpack 输出路径和命名规则
配置模块 resolve 规则
配置不同类型模块的处理规则
说明:这个配置里面只配置了.js、.vue、图片、字体等几类文件的处理规则,如果需要处理其他文件可以在 module.rules 里面另行配置。
var path = require(‘path’)
var fs = require(‘fs’)
var utils = require(‘./utils’)
var config = require(‘../config’)
var vueLoaderConfig = require(‘./vue-loader.conf’)
// 获取绝对路径
function resolve (dir) {
return path.join(__dirname, ‘..’, dir)
}
module.exports = {
// webpack 入口文件
entry: {
app: ‘./src/main.js’
},
// webpack 输出路径和命名规则
output: {
// webpack 输出的目标文件夹路径(例如:/dist)
path: config.build.assetsRoot,
// webpack 输出 bundle 文件命名格式
filename: ‘[name].js’,
// webpack 编译输出的发布路径(例如 ’//cdn.xxx.com/app/’)
publicPath: process.env.NODE_ENV === ‘production’
? config.build.assetsPublicPath
: config.dev.assetsPublicPath
},
// 模块 resolve 的规则
resolve: {
extensions: [‘.js’, ‘.vue’, ‘.json’],
// 别名,方便引用模块,例如有了别名之后,
// import Vue from ‘vue/dist/vue.common.js’ 可以写成 import Vue from ‘vue’
alias: {
‘vue$’: ‘vue/dist/vue.esm.js’,
‘@’: resolve(‘src’),
},
symlinks: false
},
// 不同类型模块的处理规则
module: {
rules: [
{// 对 src 和 test 文件夹下的.js 和.vue 文件使用 eslint-loader 进行代码规范检查
test: /\.(js|vue)$/,
loader: ‘eslint-loader’,
enforce: ‘pre’,
include: [resolve(‘src’), resolve(‘test’)],
options: {
formatter: require(‘eslint-friendly-formatter’)
}
},
{// 对所有.vue 文件使用 vue-loader 进行编译
test: /\.vue$/,
loader: ‘vue-loader’,
options: vueLoaderConfig
},
{// 对 src 和 test 文件夹下的.js 文件使用 babel-loader 将 es6+ 的代码转成 es5
test: /\.js$/,
loader: ‘babel-loader’,
include: [resolve(‘src’), resolve(‘test’)]
},
{// 对图片资源文件使用 url-loader
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
loader: ‘url-loader’,
options: {
// 小于 10K 的图片转成 base64 编码的 dataURL 字符串写到代码中
limit: 10000,
// 其他的图片转移到静态资源文件夹
name: utils.assetsPath(‘img/[name].[hash:7].[ext]’)
}
},
{// 对多媒体资源文件使用 url-loader
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
loader: ‘url-loader’,
options: {
// 小于 10K 的资源转成 base64 编码的 dataURL 字符串写到代码中
limit: 10000,
// 其他的资源转移到静态资源文件夹
name: utils.assetsPath(‘media/[name].[hash:7].[ext]’)
}
},
{// 对字体资源文件使用 url-loader
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
loader: ‘url-loader’,
options: {
// 小于 10K 的资源转成 base64 编码的 dataURL 字符串写到代码中
limit: 10000,
// 其他的资源转移到静态资源文件夹
name: utils.assetsPath(‘fonts/[name].[hash:7].[ext]’)
}
}
]
}
}
webpack.dev.conf.js
接下来看 webpack.dev.conf.js,这里面在 webpack.base.conf 的基础上增加完善了开发环境下面的配置,主要包括下面几件事情:
将 webpack 的热重载客户端代码添加到每个 entry 对应的应用
合并基础的 webpack 配置
配置样式文件的处理规则,styleLoaders
配置 Source Maps
配置 webpack 插件
var utils = require(‘./utils’)
var webpack = require(‘webpack’)
var config = require(‘../config’)
// webpack-merge 是一个可以合并数组和对象的插件
var merge = require(‘webpack-merge’)
var baseWebpackConfig = require(‘./webpack.base.conf’)
// html-webpack-plugin 用于将 webpack 编译打包后的产品文件注入到 html 模板中
// 即自动在 index.html 里面加上 <link> 和 <script> 标签引用 webpack 打包后的文件
var HtmlWebpackPlugin = require(‘html-webpack-plugin’)
// friendly-errors-webpack-plugin 用于更友好地输出 webpack 的警告、错误等信息
var FriendlyErrorsPlugin = require(‘friendly-errors-webpack-plugin’)
// add hot-reload related code to entry chunks
// 给每个入口页面 (应用) 加上 dev-client,用于跟 dev-server 的热重载插件通信,实现热更新
Object.keys(baseWebpackConfig.entry).forEach(function (name) {
baseWebpackConfig.entry[name] = [‘./build/dev-client’].concat(baseWebpackConfig.entry[name])
})
module.exports = merge(baseWebpackConfig, {
module: {
// 样式文件的处理规则,对 css/sass/scss 等不同内容使用相应的 styleLoaders
// 由 utils 配置出各种类型的预处理语言所需要使用的 loader,例如 sass 需要使用 sass-loader
rules: utils.styleLoaders({sourceMap: config.dev.cssSourceMap})
},
// cheap-module-eval-source-map is faster for development
// 使用这种 source-map 更快
devtool: ‘#cheap-module-eval-source-map’,
// webpack 插件
plugins: [
new webpack.DefinePlugin({
‘process.env’: config.dev.env
}),
// 开启 webpack 热更新功能
new webpack.HotModuleReplacementPlugin(),
// webpack 编译过程中出错的时候跳过报错阶段,不会阻塞编译,在编译结束后报错
new webpack.NoEmitOnErrorsPlugin(),
// 自动将依赖注入 html 模板,并输出最终的 html 文件到目标文件夹
new HtmlWebpackPlugin({
filename: ‘index.html’,
template: ‘index.html’,
inject: true
}),
new FriendlyErrorsPlugin()
]
})
utils
此配置文件是 vue 开发环境的 wepack 相关配置文件,主要用来处理 css-loader 和 vue-style-loader
// 引入 nodejs 路径模块
var path = require(‘path’)
// 引入 config 目录下的 index.js 配置文件
var config = require(‘../config’)
// 引入 extract-text-webpack-plugin 插件,用来将 css 提取到单独的 css 文件中
// 详情请看(1)
var ExtractTextPlugin = require(‘extract-text-webpack-plugin’)
// exports 其实就是一个对象,用来导出方法的最终还是使用 module.exports,此处导出 assetsPath
exports.assetsPath = function (_path) {
// 如果是生产环境 assetsSubDirectory 就是 ’static’,否则还是 ’static’,哈哈哈
var assetsSubDirectory = process.env.NODE_ENV === ‘production’
? config.build.assetsSubDirectory
: config.dev.assetsSubDirectory
// path.join 和 path.posix.join 的区别就是,前者返回的是完整的路径,后者返回的是完整路径的相对根路径
// 也就是说 path.join 的路径是 C:a/a/b/xiangmu/b,那么 path.posix.join 就是 b
return path.posix.join(assetsSubDirectory, _path)
// 所以这个方法的作用就是返回一个干净的相对根路径
}
// 下面是导出 cssLoaders 的相关配置
exports.cssLoaders = function (options) {
// options 如果没值就是空对象
options = options || {}
// cssLoader 的基本配置
var cssLoader = {
loader: ‘css-loader’,
options: {
// options 是用来传递参数给 loader 的
// minimize 表示压缩,如果是生产环境就压缩 css 代码
minimize: process.env.NODE_ENV === ‘production’,
// 是否开启 cssmap,默认是 false
sourceMap: options.sourceMap
}
}
// generate loader string to be used with extract text plugin
function generateLoaders (loader, loaderOptions) {
// 将上面的基础 cssLoader 配置放在一个数组里面
var loaders = [cssLoader]
// 如果该函数传递了单独的 loader 就加到这个 loaders 数组里面,这个 loader 可能是 less,sass 之类的
if (loader) {
loaders.push({
// 加载对应的 loader
loader: loader + ‘-loader’,
// Object.assign 是 es6 的方法,主要用来合并对象的,浅拷贝
options: Object.assign({}, loaderOptions, {
sourceMap: options.sourceMap
})
})
}
// Extract CSS when that option is specified
// (which is the case during production build)
// 注意这个 extract 是自定义的属性,可以定义在 options 里面,主要作用就是当配置为 true 就把文件单独提取,false 表示不单独提取,这个可以在使用的时候单独配置,瞬间觉得 vue 作者好牛逼
if (options.extract) {
return ExtractTextPlugin.extract({
use: loaders,
fallback: ‘vue-style-loader’
})
} else {
return [‘vue-style-loader’].concat(loaders)
}
// 上面这段代码就是用来返回最终读取和导入 loader,来处理对应类型的文件
}
// https://vue-loader.vuejs.org/en/configurations/extract-css.html
return {
css: generateLoaders(), // css 对应 vue-style-loader 和 css-loader
postcss: generateLoaders(), // postcss 对应 vue-style-loader 和 css-loader
less: generateLoaders(‘less’), // less 对应 vue-style-loader 和 less-loader
sass: generateLoaders(‘sass’, { indentedSyntax: true}), // sass 对应 vue-style-loader 和 sass-loader
scss: generateLoaders(‘sass’), // scss 对应 vue-style-loader 和 sass-loader
stylus: generateLoaders(‘stylus’), // stylus 对应 vue-style-loader 和 stylus-loader
styl: generateLoaders(‘stylus’) // styl 对应 vue-style-loader 和 styl-loader
}
}
// Generate loaders for standalone style files (outside of .vue)
// 下面这个主要处理 import 这种方式导入的文件类型的打包,上面的 exports.cssLoaders 是为这一步服务的
exports.styleLoaders = function (options) {
var output = []
// 下面就是生成的各种 css 文件的 loader 对象
var loaders = exports.cssLoaders(options)
for (var extension in loaders) {
// 把每一种文件的 laoder 都提取出来
var loader = loaders[extension]
output.push({
// 把最终的结果都 push 到 output 数组中,大事搞定
test: new RegExp(‘\\.’ + extension + ‘$’),
use: loader
})
}
return output
}
extract-text-webpack-plugin 插件是用来将文本从 bundle 中提取到一个单独的文件中
const ExtractTextPlugin = require(“extract-text-webpack-plugin”);
module.exports = {
module: {
rules: [
{
test: /\.css$/, // 主要用来处理 css 文件
use: ExtractTextPlugin.extract({
fallback: “style-loader”, // fallback 表示如果 css 文件没有成功导入就使用 style-loader 导入
use: “css-loader” // 表示使用 css-loader 从 js 读取 css 文件
})
}
],
plugins: [
new ExtractTextPlugin(“styles.css”) // 表示生成 styles.css 文件
]
}
}
vue-loader.conf.js
var utils = require(‘./utils’)
var config = require(‘../config’)
var isProduction = process.env.NODE_ENV === ‘production’
module.exports = {
// 处理.vue 文件中的样式
loaders: utils.cssLoaders({
// 是否打开 source-map
sourceMap: isProduction
? config.build.productionSourceMap
: config.dev.cssSourceMap,
// 是否提取样式到单独的文件
extract: isProduction
}),
transformToRequire: {
video: ‘src’,
source: ‘src’,
img: ‘src’,
image: ‘xlink:href’
}
}
dev-client.js
dev-client.js 里面主要写了浏览器端代码,用于实现 webpack 的热更新。
/* eslint-disable */
// 实现浏览器端的 EventSource,用于跟服务器双向通信
// webpack 热重载客户端跟 dev-server 上的热重载插件之间需要进行双向通信
// 服务端 webpack 重新编译后,会向客户端推送信息,告诉客户端进行更新
require(‘eventsource-polyfill’)
// webpack 热重载客户端
var hotClient = require(‘webpack-hot-middleware/client?noInfo=true&reload=true’)
// 客户端收到更新动作,执行页面刷新
hotClient.subscribe(function (event) {
if (event.action === ‘reload’) {
window.location.reload()
}
})
build.js
执行”npm run build”的时候首先执行的是 build/build.js 文件,build.js 主要完成下面几件事:
loading 动画
删除目标文件夹
执行 webpack 构建
输出信息
说明:webpack 编译之后会输出到配置里面指定的目标文件夹;删除目标文件夹之后再创建是为了去除旧的内容,以免产生不可预测的影响。
// 检查 NodeJS 和 npm 的版本
require(‘./check-versions’)()
process.env.NODE_ENV = ‘production’
// ora,一个可以在终端显示 spinner 的插件
var ora = require(‘ora’)
// rm,用于删除文件或文件夹的插件
var rm = require(‘rimraf’)
var path = require(‘path’)
// chalk,用于在控制台输出带颜色字体的插件
var chalk = require(‘chalk’)
var webpack = require(‘webpack’)
var config = require(‘../config’)
var webpackConfig = require(‘./webpack.prod.conf’)
var spinner = ora(‘building for production…’)
spinner.start() // 开启 loading 动画
// 首先将整个 dist 文件夹以及里面的内容删除,以免遗留旧的没用的文件
// 删除完成后才开始 webpack 构建打包
rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => {
if (err) throw err
// 执行 webpack 构建打包,完成之后在终端输出构建完成的相关信息或者输出报错信息并退出程序
webpack(webpackConfig, function (err, stats) {
spinner.stop()
if (err) throw err
process.stdout.write(stats.toString({
colors: true,
modules: false,
children: false,
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’
))
})
})
webpack.prod.conf.js
构建的时候用到的 webpack 配置来自 webpack.prod.conf.js,该配置同样是在 webpack.base.conf 基础上的进一步完善。主要完成下面几件事情:
合并基础的 webpack 配置
配置样式文件的处理规则,styleLoaders
配置 webpack 的输出
配置 webpack 插件
gzip 模式下的 webpack 插件配置
webpack-bundle 分析
说明:webpack 插件里面多了丑化压缩代码以及抽离 css 文件等插件。
var path = require(‘path’)
var utils = require(‘./utils’)
var webpack = require(‘webpack’)
var config = require(‘../config’)
var merge = require(‘webpack-merge’)
var baseWebpackConfig = require(‘./webpack.base.conf’)
// copy-webpack-plugin,用于将 static 中的静态文件复制到产品文件夹 dist
var CopyWebpackPlugin = require(‘copy-webpack-plugin’)
var HtmlWebpackPlugin = require(‘html-webpack-plugin’)
var ExtractTextPlugin = require(‘extract-text-webpack-plugin’)
// optimize-css-assets-webpack-plugin,用于优化和最小化 css 资源
var OptimizeCSSPlugin = require(‘optimize-css-assets-webpack-plugin’)
var env = config.build.env
var webpackConfig = merge(baseWebpackConfig, {
module: {
// 样式文件的处理规则,对 css/sass/scss 等不同内容使用相应的 styleLoaders
// 由 utils 配置出各种类型的预处理语言所需要使用的 loader,例如 sass 需要使用 sass-loader
rules: utils.styleLoaders({
sourceMap: config.build.productionSourceMap,
extract: true
})
},
// 是否使用 source-map
devtool: config.build.productionSourceMap ? ‘#source-map’ : false,
// webpack 输出路径和命名规则
output: {
path: config.build.assetsRoot,
filename: utils.assetsPath(‘js/[name].[chunkhash].js’),
chunkFilename: utils.assetsPath(‘js/[id].[chunkhash].js’)
},
// webpack 插件
plugins: [
// http://vuejs.github.io/vue-loader/en/workflow/production.html
new webpack.DefinePlugin({
‘process.env’: env
}),
// 丑化压缩 JS 代码
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
},
sourceMap: true
}),
// extract css into its own file
// 将 css 提取到单独的文件
new ExtractTextPlugin({
filename: utils.assetsPath(‘css/[name].[contenthash].css’)
}),
// Compress extracted CSS. We are using this plugin so that possible
// duplicated CSS from different components can be deduped.
// 优化、最小化 css 代码,如果只简单使用 extract-text-plugin 可能会造成 css 重复
// 具体原因可以看 npm 上面 optimize-css-assets-webpack-plugin 的介绍
new OptimizeCSSPlugin({
cssProcessorOptions: {
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
// 将产品文件的引用注入到 index.html
new HtmlWebpackPlugin({
filename: config.build.index,
template: ‘index.html’,
inject: true,
minify: {
// 删除 index.html 中的注释
removeComments: true,
// 删除 index.html 中的空格
collapseWhitespace: true,
// 删除各种 html 标签属性值的双引号
removeAttributeQuotes: true
// more options:
// https://github.com/kangax/html-minifier#options-quick-reference
},
// necessary to consistently work with multiple chunks via CommonsChunkPlugin
// 注入依赖的时候按照依赖先后顺序进行注入,比如,需要先注入 vendor.js,再注入 app.js
chunksSortMode: ‘dependency’
}),
// keep module.id stable when vender modules does not change
new webpack.HashedModuleIdsPlugin(),
// split vendor js into its own file
// 将所有从 node_modules 中引入的 js 提取到 vendor.js,即抽取库文件
new webpack.optimize.CommonsChunkPlugin({
name: ‘vendor’,
minChunks: function (module, count) {
// 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
// 从 vendor 中提取出 manifest,原因如上
new webpack.optimize.CommonsChunkPlugin({
name: ‘manifest’,
chunks: [‘vendor’]
}),
// copy custom static assets
// 将 static 文件夹里面的静态资源复制到 dist/static
new CopyWebpackPlugin([
{
from: path.resolve(__dirname, ‘../static’),
to: config.build.assetsSubDirectory,
ignore: [‘.*’]
}
])
]
})
// 如果开启了产品 gzip 压缩,则利用插件将构建后的产品文件进行压缩
if (config.build.productionGzip) {
// 一个用于压缩的 webpack 插件
var CompressionWebpackPlugin = require(‘compression-webpack-plugin’)
webpackConfig.plugins.push(
new CompressionWebpackPlugin({
asset: ‘[path].gz[query]’,
// 压缩算法
algorithm: ‘gzip’,
test: new RegExp(
‘\\.(‘ +
config.build.productionGzipExtensions.join(‘|’) +
‘)$’
),
threshold: 10240,
minRatio: 0.8
})
)
}
// 如果启动了 report,则通过插件给出 webpack 构建打包后的产品文件分析报告
if (config.build.bundleAnalyzerReport) {
var BundleAnalyzerPlugin = require(‘webpack-bundle-analyzer’).BundleAnalyzerPlugin
webpackConfig.plugins.push(new BundleAnalyzerPlugin())
}
module.exports = webpackConfig
check-versions.js
// chalk, 用于在控制台输出带颜色字体的插件
var chalk = require(‘chalk’)
// semver, 语义化版本检查插件(The semantic version parser used by npm)
var semver = require(‘semver’)
var packageConfig = require(‘../package.json’)
// shelljs, 执行 Unix 命令行的插件
var shell = require(‘shelljs’)
// 开辟子进程执行指令 cmd 并返回结果
function exec (cmd) {
return require(‘child_process’).execSync(cmd).toString().trim()
}
// node 和 npm 版本需求
var versionRequirements = [
{
name: ‘node’,
currentVersion: semver.clean(process.version),
versionRequirement: packageConfig.engines.node
}
]
if (shell.which(‘npm’)) {
versionRequirements.push({
name: ‘npm’,
currentVersion: exec(‘npm –version’),
versionRequirement: packageConfig.engines.npm
})
}
module.exports = function () {
var warnings = []
// 依次判断版本是否符合要求
for (var i = 0; i < versionRequirements.length; i++) {
var 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 (var i = 0; i < warnings.length; i++) {
var warning = warnings[i]
console.log(‘ ‘ + warning)
}
console.log()
process.exit(1)
}
}
config
index.js
config 文件夹下最主要的文件就是 index.js 了,在这里面描述了开发和构建两种环境下的配置,前面的 build 文件夹下也有不少文件引用了 index.js 里面的配置
// see http://vuejs-templates.github.io/webpack for documentation.
var path = require(‘path’)
module.exports = {
// 构建产品时使用的配置
build: {
// 环境变量
env: require(‘./prod.env’),
// html 入口文件
index: path.resolve(__dirname, ‘../dist/index.html’),
// 产品文件的存放路径
assetsRoot: path.resolve(__dirname, ‘../dist’),
// 二级目录,存放静态资源文件的目录,位于 dist 文件夹下
assetsSubDirectory: ‘static’,
// 发布路径,如果构建后的产品文件有用于发布 CDN 或者放到其他域名的服务器,可以在这里进行设置
// 设置之后构建的产品文件在注入到 index.html 中的时候就会带上这里的发布路径
assetsPublicPath: ‘/’,
// 是否使用 source-map
productionSourceMap: true,
// Gzip off by default as many popular static hosts such as
// Surge or Netlify already gzip all static assets for you.
// Before setting to `true`, make sure to:
// npm install –save-dev compression-webpack-plugin
// 是否开启 gzip 压缩
productionGzip: false,
// gzip 模式下需要压缩的文件的扩展名,设置 js、css 之后就只会对 js 和 css 文件进行压缩
productionGzipExtensions: [‘js’, ‘css’],
// Run the build command with an extra argument to
// View the bundle analyzer report after build finishes:
// `npm run build –report`
// Set to `true` or `false` to always turn it on or off
// 是否展示 webpack 构建打包之后的分析报告
bundleAnalyzerReport: process.env.npm_config_report
},
// 开发过程中使用的配置
dev: {
// 环境变量
env: require(‘./dev.env’),
// dev-server 监听的端口
port: 8080,
// 是否自动打开浏览器
autoOpenBrowser: true,
// 静态资源文件夹
assetsSubDirectory: ‘static’,
// 发布路径
assetsPublicPath: ‘/’,
// 代理配置表,在这里可以配置特定的请求代理到对应的 API 接口
// 例如将 ’localhost:8080/api/xxx’ 代理到 ’www.example.com/api/xxx’
proxyTable: {},
// CSS Sourcemaps off by default because relative paths are “buggy”
// with this option, according to the CSS-Loader README
// (https://github.com/webpack/css-loader#sourcemaps)
// In our experience, they generally work as expected,
// just be aware of this issue when enabling this option.
// 是否开启 cssSourceMap
cssSourceMap: false
}
}
‘use strict’
const path = require(‘path’)
module.exports = {
dev: {
// 开发环境下面的配置
assetsSubDirectory: ‘static’,// 子目录,一般存放 css,js,image 等文件
assetsPublicPath: ‘/’,// 根目录
proxyTable: {},// 可利用该属性解决跨域的问题
host: ‘localhost’, // 地址
port: 8080, // 端口号设置,端口号占用出现问题可在此处修改
autoOpenBrowser: false,// 是否在编译(输入命令行 npm run dev)后打开 http://localhost:8080/ 页面,以前配置为 true,近些版本改为 false,个人偏向习惯自动打开页面
errorOverlay: true,// 浏览器错误提示
notifyOnErrors: true,// 跨平台错误提示
poll: false, // 使用文件系统 (file system) 获取文件改动的通知 devServer.watchOptions
devtool: ‘cheap-module-eval-source-map’,// 增加调试,该属性为原始源代码(仅限行)不可在生产环境中使用
cacheBusting: true,// 使缓存失效
cssSourceMap: true// 代码压缩后进行调 bug 定位将非常困难,于是引入 sourcemap 记录压缩前后的位置信息记录,当产生错误时直接定位到未压缩前的位置,将大大的方便我们调试
},
build: {
// 生产环境下面的配置
index: path.resolve(__dirname, ‘../dist/index.html’),//index 编译后生成的位置和名字,根据需要改变后缀,比如 index.php
assetsRoot: path.resolve(__dirname, ‘../dist’),// 编译后存放生成环境代码的位置
assetsSubDirectory: ‘static’,//js,css,images 存放文件夹名
assetsPublicPath: ‘/’,// 发布的根目录,通常本地打包 dist 后打开文件会报错,此处修改为./。如果是上线的文件,可根据文件存放位置进行更改路径
productionSourceMap: true,
devtool: ‘#source-map’,//①
//unit 的 gzip 命令用来压缩文件,gzip 模式下需要压缩的文件的扩展名有 js 和 css
productionGzip: false,
productionGzipExtensions: [‘js’, ‘css’],
bundleAnalyzerReport: process.env.npm_config_report
}
}
prod.env.js
当开发是调取 dev.env.js 的开发环境配置,发布时调用 prod.env.js 的生产环境配置
‘use strict’
module.exports = {
NODE_ENV: ‘”production”‘
}
dev.env.js
config 内的文件其实是服务于 build 的,大部分是定义一个变量 export 出去。
‘use strict’// 采用严格模式
const merge = require(‘webpack-merge’)//①
const prodEnv = require(‘./prod.env’)
//webpack-merge 提供了一个合并函数,它将数组和合并对象创建一个新对象。
// 如果遇到函数,它将执行它们,通过算法运行结果,然后再次将返回的值封装在函数中. 这边将 dev 和 prod 进行合并
module.exports = merge(prodEnv, {
NODE_ENV: ‘”development”‘
})
src
①、assets 文件:脚手架自动会放入一个图片在里面作为初始页面的 logo。平常我们使用的时候会在里面建立 js,css,img,fonts 等文件夹,作为静态资源调用
②、components 文件夹:用来存放组件,合理地使用组件可以高效地实现复用等功能,从而更好地开发项目。一般情况下比如创建头部组件的时候,我们会新建一个 header 的文件夹,然后再新建一个 header.vue 的文件
③、router 文件夹:该文件夹下有一个叫 index.js 文件,用于实现页面的路由跳转,具体使用请点击→vue-router 传送门
④、App.vue:作为我们的主组件,可通过使用 <router-view/> 开放入口让其他的页面组件得以显示。
⑤、main.js:作为我们的入口文件,主要作用是初始化 vue 实例并使用需要的插件,小型项目省略 router 时可放在该处
.babelrc
{
// 制定转码的规则
“presets”: [
//env 是使用 babel-preset-env 插件将 js 进行转码成 es5,并且设置不转码的 AMD,COMMONJS 的模块文件,制定浏览器的兼容
[“env”, {
“modules”: false,
“targets”: {
“browsers”: [“> 1%”, “last 2 versions”, “not ie <= 8”]
}
}],
“stage-2”
],
“plugins”: [“transform-vue-jsx”, “transform-runtime”]//①
}
.postcessrc.js
.postcssrc.js 文件其实是 postcss-loader 包的一个配置,在 webpack 的旧版本可以直接在 webpack.config.js 中配置,现版本中 postcss 的文档示例独立出.postcssrc.js,里面写进去需要使用到的插件
module.exports = {
“plugins”: {
“postcss-import”: {},//①
“postcss-url”: {},//②
“autoprefixer”: {}//③
}
}
package.json
package.json 来制定名单,需要哪些 npm 包来参与到项目中来,npm install 命令根据这个配置文件增减来管理本地的安装包
{
// 从 name 到 private 都是 package 的配置信息,也就是我们在脚手架搭建中输入的项目描述
“name”: “shop”,// 项目名称:不能以.(点)或者_(下划线)开头,不能包含大写字母,具有明确的的含义与现有项目名字不重复
“version”: “1.0.0”,// 项目版本号:遵循“大版本. 次要版本. 小版本”
“description”: “A Vue.js project”,// 项目描述
“author”: “qietuniu”,// 作者名字
“private”: true,// 是否私有
//scripts 中的子项即是我们在控制台运行的脚本的缩写
“scripts”: {
//①webpack-dev-server: 启动了 http 服务器,实现实时编译;
//inline 模式会在 webpack.config.js 入口配置中新增 webpack-dev-server/client?http://localhost:8080/ 的入口, 使得我们访问路径为 localhost:8080/index.html(相应的还有另外一种模式 Iframe);
//progress: 显示打包的进度
“dev”: “webpack-dev-server –inline –progress –config build/webpack.dev.conf.js”,
“start”: “npm run dev”,// 与 npm run dev 相同,直接运行开发环境
“build”: “node build/build.js”// 使用 node 运行 build 文件
},
//②dependencies(项目依赖库): 在安装时使用 –save 则写入到 dependencies
“dependencies”: {
“vue”: “^2.5.2”,//vue.js
“vue-router”: “^3.0.1″//vue 的路由插件
},
// 和 devDependencies(开发依赖库):在安装时使用 –save-dev 将写入到 devDependencies
“devDependencies”: {
“autoprefixer”: “^7.1.2”,//autoprefixer 作为 postcss 插件用来解析 CSS 补充前缀,例如 display: flex 会补充为 display:-webkit-box;display: -webkit-flex;display: -ms-flexbox;display: flex。
//babel: 以下几个 babel 开头的都是针对 es6 解析的插件。用最新标准编写的 JavaScript 代码向下编译成可以在今天随处可用的版本
“babel-core”: “^6.22.1”,//babel 的核心,把 js 代码分析成 ast,方便各个插件分析语法进行相应的处理。
“babel-helper-vue-jsx-merge-props”: “^2.0.3”,// 预制 babel-template 函数,提供给 vue,jsx 等使用
“babel-loader”: “^7.1.1”,// 使项目运行使用 Babel 和 webpack 来传输 js 文件,使用 babel-core 提供的 api 进行转译
“babel-plugin-syntax-jsx”: “^6.18.0”,// 支持 jsx
“babel-plugin-transform-runtime”: “^6.22.0”,// 避免编译输出中的重复,直接编译到 build 环境中
“babel-plugin-transform-vue-jsx”: “^3.5.0”,//babel 转译过程中使用到的插件,避免重复
“babel-preset-env”: “^1.3.2”,// 转为 es5,transform 阶段使用到的插件之一
“babel-preset-stage-2”: “^6.22.0”,//ECMAScript 第二阶段的规范
“chalk”: “^2.0.1”,// 用来在命令行输出不同颜色文字
“copy-webpack-plugin”: “^4.0.1”,// 拷贝资源和文件
“css-loader”: “^0.28.0”,//webpack 先用 css-loader 加载器去解析后缀为 css 的文件,再使用 style-loader 生成一个内容为最终解析完的 css 代码的 style 标签,放到 head 标签里
“extract-text-webpack-plugin”: “^3.0.0”,// 将一个以上的包里面的文本提取到单独文件中
“file-loader”: “^1.1.4”,//③打包压缩文件,与 url-loader 用法类似
“friendly-errors-webpack-plugin”: “^1.6.1”,// 识别某些类别的 WebPACK 错误和清理,聚合和优先排序,以提供更好的开发经验
“html-webpack-plugin”: “^2.30.1”,// 简化了 HTML 文件的创建,引入了外部资源,创建 html 的入口文件,可通过此项进行多页面的配置
“node-notifier”: “^5.1.2”,// 支持使用 node 发送跨平台的本地通知
“optimize-css-assets-webpack-plugin”: “^3.2.0”,// 压缩提取出的 css,并解决 ExtractTextPlugin 分离出的 js 重复问题(多个文件引入同一 css 文件)
“ora”: “^1.2.0”,// 加载(loading)的插件
“portfinder”: “^1.0.13”,// 查看进程端口
“postcss-import”: “^11.0.0”,// 可以消耗本地文件、节点模块或 web_modules
“postcss-loader”: “^2.0.8”,// 用来兼容 css 的插件
“postcss-url”: “^7.2.1”,//URL 上重新定位、内联或复制
“rimraf”: “^2.6.0”,// 节点的 UNIX 命令 RM—RF, 强制删除文件或者目录的命令
“semver”: “^5.3.0”,// 用来对特定的版本号做判断的
“shelljs”: “^0.7.6”,// 使用它来消除 shell 脚本在 UNIX 上的依赖性,同时仍然保留其熟悉和强大的命令,即可执行 Unix 系统命令
“uglifyjs-webpack-plugin”: “^1.1.1”,// 压缩 js 文件
“url-loader”: “^0.5.8”,// 压缩文件,可将图片转化为 base64
“vue-loader”: “^13.3.0”,//VUE 单文件组件的 WebPACK 加载器
“vue-style-loader”: “^3.0.1”,// 类似于样式加载程序,您可以在 CSS 加载器之后将其链接,以将 CSS 动态地注入到文档中作为样式标签
“vue-template-compiler”: “^2.5.2”,// 这个包可以用来预编译 VUE 模板到渲染函数,以避免运行时编译开销和 CSP 限制
“webpack”: “^3.6.0”,// 打包工具
“webpack-bundle-analyzer”: “^2.9.0”,// 可视化 webpack 输出文件的大小
“webpack-dev-server”: “^2.9.1”,// 提供一个提供实时重载的开发服务器
“webpack-merge”: “^4.1.0″// 它将数组和合并对象创建一个新对象。如果遇到函数,它将执行它们,通过算法运行结果,然后再次将返回的值封装在函数中
},
//engines 是引擎,指定 node 和 npm 版本
“engines”: {
“node”: “>= 6.0.0”,
“npm”: “>= 3.0.0”
},
// 限制了浏览器或者客户端需要什么版本才可运行
“browserslist”: [
“> 1%”,
“last 2 versions”,
“not ie <= 8”
]
}
注释:
①、点这里→webpack 运行时的配置文档传送门
②、devDependencies 和 dependencies 的区别:devDependencies 里面的插件只用于开发环境,不用于生产环境,即辅助作用,打包的时候需要,打包完成就不需要了。而 dependencies 是需要发布到生产环境的,自始至终都在。比如 wepack 等只是在开发中使用的包就写入到 devDependencies,而像 vue 这种项目全程依赖的包要写入到 dependencies
点这里→更多安装包文档搜索页传送门
③、file-loader 和 url-loader 的区别:以图片为例,file-loader 可对图片进行压缩,但是还是通过文件路径进行引入,当 http 请求增多时会降低页面性能,而 url-loader 通过设定 limit 参数,小于 limit 字节的图片会被转成 base64 的文件,大于 limit 字节的将进行图片压缩的操作。总而言之,url-loader 是 file-loader 的上层封装。
点这里→file-loader 和 url-loader 详解
点这里→file-loader 文档传送门
点这里→url-loader 文档传送门
其他文件
①、.editorconfig:编辑器的配置文件
②、.gitignore:忽略 git 提交的一个文件,配置之后提交时将不会加载忽略的文件
③、index.html:页面入口,经过编译之后的代码将插入到这来。
④、package.lock.json:锁定安装时的包的版本号,并且需要上传到 git,以保证其他人在 npm install 时大家的依赖能保证一致
⑤、README.md:可此填写项目介绍
⑥、node_modules:根据 package.json 安装时候生成的的依赖(安装包)
其他好的博客笔记
vue-cli 的 webpack 模板项目配置文件分析
vue-cli 脚手架中 webpack 配置基础文件详解