开篇前记
入门前端有一段时间了,从大学就开始学习前端,到现在刚好毕业就整整4年,其中学习了不少东西,也新出不了少东西,前段的发展总是很快的,一不小心之前所学的技术就开始落后了。以前刚开始学的时候还在学html,css,js三件套,当然还会有JQuery,现在有些人入门都开始直接学Vue框架了。踩过很多坑。但是坑还要一个一个地爬出来,未知的东西还是要一个一个地探索,所以才有了冲动来写这篇文章,第一是为了总结我之前学过的知识和经验,比如Vue全家桶,wepback构建Vue环境等。其次是在我没有实践过的领域进行探索的同时记录下来拿,以后有需要的时候再看回这篇文章。
摘要
我把这篇文章的内容拆成几个版块,作为一个连载的文章。首先列一下主要章节内容:
- 搭建一个运行Vue的webpack环境
- 加入Vue全家桶
- 编写项目工程代码
- 总结Vue测试
- 关于项目的持续集成
- 项目总结
因为在编写项目的时候总会附上对应的测试代码,但这样有时候需要看关于Vue测试的内容时会过于零散,所以在写完项目之后专门给了一个小节来写关于Vue的测试内容。当然如果你是从头往下看的话可以跳过这一小节。最后项目 弄好了按照国际惯例总是要总结一下经验的。
搭建运行Vue的webpack环境
工程目录
|____.gitignore
|____index.html
|____LICENSE
|____package-lock.json
|____package.json
|____README.md
|____src
| |____App.vue
| |____assets
| |____components
| |____main.js
| |____view
|____static
|____test
|____webpack.config.js
webpack配置
这是我摸索了几个钟弄出来的配置(不得不说webpack的配置是真的麻烦),以前都是懒所以直接使用vue-cli初始化出来的项目,webpack配置什么的都已经帮我配好了,这次自己动刀搭了一下才发现这么费劲,期间不断地报错,再加上由于用了4.0版本,也出现了一些坑,这里一并也记录一下,以后再遇到的时候好回头翻翻。
首先是最基础的webpack框架,不清楚的可以去到webpack官网上找(现在都已经有翻译很好的中文网啦,哪像以前那么苦还要去啃英文 (T^T) )。当然我们也只会用到其中的几个属性。
const path = require('path')
module.exports = {
entry: '',
output: {},
module: {
rules: []
},
plugins: []
}
OK,这就完成了webpack最基本的框架,接下来我们要分别搭建不同方式启动webpack的方案。(其实这两者在实现原理上本没有什么区别,而对于我们使用者来说区别最大的大概就是工程目录的划分和关于wepback-dev-server的配置)
基于webpack-dev-server插件来启动webpack
基于webpack-dev-server这种方式启动webpack,我们首先把之前的webpack的配置文件命名为webpack.config.js并放在项目的根目录下。然后在package.json里的script对象添加一个dev属性,并在后面添加脚本webpack-dev-server再保存。例如:
// package.json
{
...
"scripts": {
"dev": "webpack-dev-server",
"test": "echo \"Error: no test specified\" && exit 1"
},
...
}
然后我们在webpack.config.js文件里添加devServer配置:
// webpack.config.js
const path = require('path')
module.exports = {
entry: './src/main.js',
output: {
path: path.resolve(__dirname, './dist'),
publicPath: '/dist/',
filename: 'build.js'
},
module: {
rules: []
},
plugins: [],
devServer: {
// contentBase: path.join(__dirname, "dist"),
// 确保 publicPath 总是以斜杠(/)开头和结尾。
// 默认会取output.path的值,设置之后会覆盖使用devServer.publicPath的值
// publicPath: "/",
historyApiFallback: true,
compress: true,
proxy: { // proxy URLs to backend development server
// '/api': 'http://localhost:3000',
// '/api': {
// target: 'http://localhost:3000',
// pathRewrite: {'^/api' : ''}
// }
},
hot: true,
inline: true
},
devtool: '#eval-source-map'
}
这样我们在项目目录中执行npm run dev命令就可以把webpack-dev-server启动起来啦~~
基于node + express 方式来启动webpack
基本可以运行Vue项目的webpack配置
完成了上面的步骤之后,接下来就是配置vue相关的东西了,先回想在使用vue开发的时候,我们会新建一个vue后缀的文件,所以我们就需要一个loader来解析和读取.vue文件的内容。先安装:
npm install --save-dev vue-loader vue-template-compiler
然后我们再在wepback配置中的module.rules中使用:
// webpack.config.js/webpack.base.conf.js
module.exports = {
...
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader',
options: {}
}
]
},
...
至于这里为什么要安装vue-template-compiler,是因为单纯使用vue-loader启动的时候会报错,提示在没有vue-template-compiler的情况下使用vue-loader,我在网上查了一下也有人写了blog说明了这一情况,意思大概是升级到webpack 4.0之后会出现的问题。为了解决这个问题我们要在webpack配置上使用一下。
// webpack.config.js/webpack.base.conf.js
const path = require('path')
const VueLoaderPlugin = require('vue-loader/lib/plugin');
module.exports = {
...
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader',
options: {}
}
]
},
plugins: [
new VueLoaderPlugin()
],
...
现在就可以读vue文件了,接下来就是对vue文件里的css、非标准的js、图片分别配置,也让webpack可以有能力解析。
解析css(css-loader是解析css的,style-loader是把css以内嵌的方式注入到html中):
npm install --save-dev style-loader css-loader
解析非标准的js(使用babel):
npm install --save-dev babel-loader babel-preset-env
解析img:
npm install --save-dev file-loader
然后再分别写到rules里面,就完成了。整个配置如下:
// webpack.config.js/webpack.base.conf.js
const path = require('path')
const VueLoaderPlugin = require('vue-loader/lib/plugin');
module.exports = {
entry: './src/main.js',
output: {
path: path.resolve(__dirname, './dist'),
publicPath: '/dist/',
filename: 'build.js'
},
module: {
rules: [{
test: /\.css$/,
use: [
'vue-style-loader',
'css-loader'
]
},
{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/
},
{
test: /\.vue$/,
loader: 'vue-loader',
options: {}
},
{
test: /\.(png|jpg|gif|svg)$/,
loader: 'file-loader',
options: {
name: '[name].[ext]?[hash]'
}
}
]
},
plugins: [
new VueLoaderPlugin()
],
devServer: {
// contentBase: path.join(__dirname, "dist"),
// 确保 publicPath 总是以斜杠(/)开头和结尾。
// 默认会取output.path的值,设置之后会覆盖使用devServer.publicPath的值
// publicPath: "/",
historyApiFallback: true,
compress: true,
proxy: { // proxy URLs to backend development server
// '/api': 'http://localhost:3000',
// '/api': {
// target: 'http://localhost:3000',
// pathRewrite: {'^/api' : ''}
// }
},
hot: true,
inline: true
},
devtool: '#eval-source-map'
}
现在一份可以跑Vue的配置文件就出来了,不过现在构建出来的文件没一个有页面来将它们显示出来,所以要新建一个html文件,然后加载构建出来的文件再显示。
新建一份Html文件:
// index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Vue Project Testing</title>
</head>
<body>
<div id="app"></div>
</body>
</html>
我们需要在构建的时候把它设置成模版文件,使用html-webpack-plugin插件可以轻松完成工作。引入方式如下:
// webpack.conf.js
...
const HtmlWebpackPlugin = require('html-webpack-plugin');
...
module.exports = {
...
plugins: [
new HtmlWebpackPlugin({
template: 'index.html',
filename: 'index.html',
inject: true
}),
],
...
}
但是这样子我们在开发时修改源文件是不会触发热更新的,所以现在在dev-server上配上热更新:
// webpack.conf.js
...
const webpack = require('webpack');
...
module.exports = {
...
plugins: [
...
new webpack.NamedModulesPlugin(),
new webpack.HotModuleReplacementPlugin()
...
],
...
devServer: {
...
inline: true,
hot: true
...
}
}
需要做的更改有:引入webpack,然后在添加webpack.NamedModulesPlugin和webpack.HotModuleReplacementPlugin插件,然后在devServer配置中把inline和hot设置成true。这样就可以进行热更新了。
这里引入
webpack.NamedModulesPlugin是为了更容易查看要修补(patch)的依赖。
环境区分
按照刚刚的步骤进行操作的话,会发现当项目运行起来的时候webpack会报如下错误,现在慢慢地按照提示来对项目进行优化。
WARNING in configuration
The 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/concepts/mode/
翻译成中文的意思大概是:mode这个选项没有被设置,webpack将会默认设置为production。你可以把mode选项设置为development或者production,以让webpack以指定的环境启动。你也可以设置为none以禁用任何的默认行为。了解更多:https://webpack.js.org/concepts/mode/。
这个问题主要是告诉我们要让webpack知道现是要按什么环境去构建应用,通常我们会通过一个配置文件来记录所有会用到的环境,然后在调用的时候通过传入参数的形式来指定使用哪一个环境。
我们新建三个文件:webpack.base.conf.js、webpack.dev.conf.js、webpack.prod.conf.js,我们会把生产和开发共同用到的配置写在webpack.base.conf.js文件中,然后对于开发和生产独有的配置分别写在webpack.dev.conf.js和webpack.prod.conf.js文件中,并且它们会使用一个叫webpack-merge的插件把webpack.base.conf.js的配置合并起来,最后再通过npm script的方式来分别调用哪一份配置,从而达到区分环境的目的。而对于开发时要对运行环境进行判断(比如在开发时需要更详细的log输出,更友好的代码错误提示等),则可以使用webpack.DefinePlugin这个插件。然后在代码进行if (process.env.NODE_ENV !== 'production') {}判断即可。
// webpack.base.conf.js
const path = require('path')
const VueLoaderPlugin = require('vue-loader/lib/plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry: ['./src/main.js'],
output: {
path: path.resolve(__dirname, './dist'),
// publicPath: '/dist/',
filename: 'build.js'
},
resolve: {
extensions: ['.js', '.vue', '.json'],
alias: {
'vue$': 'vue/dist/vue.esm.js'
}
},
module: {
rules: [{
test: /\.css$/,
use: [
'vue-style-loader',
'css-loader'
]
},
{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/
},
{
test: /\.vue$/,
loader: 'vue-loader',
options: {}
},
{
test: /\.(png|jpg|gif|svg)$/,
loader: 'file-loader',
options: {
name: '[name].[ext]?[hash]'
}
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: 'index.html',
filename: 'index.html',
inject: true
}),
new VueLoaderPlugin()
]
}
在webpack.base.conf.js配置中只保存了入口文件、资源输出和一些loader的相关配置等。
// webpack.dev.conf.js
const path = require('path')
const webpack = require('webpack');
const merge = require('webpack-merge');
const baseConfig = require('./webpack.base.conf');
module.exports = merge(baseConfig, {
devtool: 'inline-source-map',
devServer: {
contentBase: './dist'
},
mode: 'development',
devtool: 'inline-source-map',
plugins: [
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('production')
}),
new webpack.NamedModulesPlugin(),
new webpack.HotModuleReplacementPlugin()
],
devServer: {
contentBase: path.resolve(__dirname, './dist'),
watchContentBase: true,
inline: true,
hot: true
}
});
在webpack.dev.conf.js中,我们指定了mode为development,devtool为错误提示更友好的inline-source-map模式,并且简单配置了devServer,还有一些相关的plugins。
// webpack.prod.conf.js
const CleanWebpackPlugin = require('clean-webpack-plugin');
const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
const webpack = require('webpack');
const merge = require('webpack-merge');
const baseConfig = require('./webpack.base.conf');
module.exports = merge(baseConfig, {
devtool: 'source-map',
mode: 'production',
plugins: [
new CleanWebpackPlugin(['dist']),
new UglifyJSPlugin({
sourceMap: true
}),
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('production')
})
],
devtool: 'source-map',
optimization: {
runtimeChunk: {
name: "manifest"
},
splitChunks: {
cacheGroups: {
commons: {
test: /[\\/]node_modules[\\/]/,
name: "vendor",
chunks: "all"
}
}
}
}
});
webpack.prod.conf.js配置了清除dist目录的clean-webpack-plugin插件,devtool设置为性能更优的source-map模式(如果是inline-source-map模式,打包出来的包会很大,webpack会像打印出相关警告),还使用了代码压缩插件uglifyjs-webpack-plugin,最后简单配置一下optimization里的切割代码。
最后在package.json里给dev和build脚本分别指定对应的webpack配置即可:
// package.json
"scripts": {
"dev": "webpack-dev-server --config webpack.dev.conf.js",
"build": "webpack --config webpack.prod.conf.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
在使用
webpack.DefinePlugin这个插件对webpack本身的配置无效,如果需要在webpack配置中进行判断,则可以使用cross-env,然后修改package.json文件里的运行脚本命令,添加cross-env NODE_ENV=[prototype]在命令前面即可,如:
// package.json
...
"scripts": {
"dev": "cross-env NODE_ENV=development webpack-dev-server --config webpack.dev.conf.js",
"build": "webpack --config webpack.prod.conf.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
...
这里的
process.env是node.js的一个环境变量对象,挂载到这个对象上的属性可以在node的全局运行环境中访问到,而NODE_ENV是node社区约定作为当前运行环境的属性(当然你也可以用其它属性代替)。
这个时候再npm run dev就会使用开发相关的配置,执行npm run build就会用生产的配置来执行构建工作。
配置优化
WARNING in asset size limit
The following asset(s) exceed the recommended size limit (244 KiB).
This can impact web performance.
Assets:
build.js (2.13 MiB)
这个警告的意思大概是build.js这个资源的大小(2.13MB)超过了建议的244KB这个限制,这会影响到页面的性能。
> Vue_Project_Testing@1.0.0 build /Users/kuntang/Documents/learning/web_testing/Vue_Project_Testing/Charpter_03
> webpack
/Users/kuntang/Documents/learning/web_testing/Vue_Project_Testing/Charpter_03/node_modules/_webpack-cli@3.1.0@webpack-cli/bin/cli.js:244
throw err;
^
Error: webpack.optimize.CommonsChunkPlugin has been removed, please use config.optimization.splitChunks instead.
at Object.get [as CommonsChunkPlugin] (/Users/kuntang/Documents/learning/web_testing/Vue_Project_Testing/Charpter_03/node_modules/_webpack@4.16.1@webpack/lib/webpack.js:173:10)
这里说的是在webpack 3.x中使用的webpack.optimize.CommonsChunkPlugin插件在新版本中已经被移除了,要使用这个功能,则需要在配置中进行相关设置就可以了:
// webpack.conf.js
module.exports = {
entry: ['babel-polyfill', './src/main.js'],
...
optimization: {
splitChunks: true
},
...
}
tips: 提示信息中
config.optimization.splitChunks中config指的是当前使用的webpack配置文件,而optimization.splitChunks指的是配置文件中optimization对象里的splitChunks属性。
WARNING in entrypoint size limit
The following entrypoint(s) combined asset size exceeds the recommended limit (244 KiB). This can impact web performance.
Entrypoints:
main (2.13 MiB)
build.js
0.9256098444c5c2f2e51c.hot-update.js
这里的提示跟上面的差不多,也是指入口文件main.js体积太大了,不在建议的限制值内,会影响性能。
WARNING in webpack performance recommendations
You can limit the size of your bundles by using import() or require.ensure to lazy load some parts of your application.
For more info visit https://webpack.js.org/guides/code-splitting/
这里的提示是说你可以使用ipmort()或者require.ensure的懒加载方式来加载你应用的部分资源,从而减少你打包后单个文件的大小。
以上是webpack的打包优化的部分,对于webpack的优化很多人有不同的解决方案,而且还要根据实际的业务来作针对的优化,所以说这是一个无了期的话题,对于我自己本身对webpack的了解只仅限于文档中,没有对这方面作过多的研究,所以这部分仅仅作一个非常简单的介绍,以后有时间再总结补充