0. 前言
首先不得不说 vue-cli + vue-webpack 模版真的很方便,vue init webpack my-vue-project 就搭好框架了,而且开发环境生产环境都有了。npm run dev 启动开发环境,npm run build 发布生产环境。几个命令全部搞定了。但是模版有时候可能不够灵活,或者我们想修改其中一些东西,面对这些需求的时候,理解怎么搭建起这个 vue + webpack 的环境还是很有用的。那最快捷的方式,当然是自己从头开始搭一遍啦。
1. 新建项目
因为不用 vue-cli 和 vue 模版,所以一开始我们就建个空文件夹。
mkdir my-vue-project
cd my-vue-project
npm init # 初始化项目。这时候会问你一些问题,比如项目名称、作者之类的,照着提示回答问题就好
2. 安装基本的 npm 包
首先肯定要安装 vue 和 webpack。然后 webpack 4.x已经把 cli 单独拎出来了,所以还要安装 webpack-cli;然后因为 webpack 本身其实直接能处理的只有 js 资源,是通过各种 loader 让其他资源可以被 webpack 打包处理的。那么现在我们要用 vue 写单文件组件(就是 .vue 文件),所以就还要安装 vue-loader。
npm install vue vue-loader webpack webpack-cli --save-dev # --save-dev表示这些只是开发环境所需的依赖
3. 添加项目各种入口文件
入口文件都是很普遍的那种,比如根目录下的 index.html
,src
文件夹下的 main.js
,app.vue
等等,我就不一一解释了。
添加入口文件后的项目目录如下:
.
├── dist/
| ├── ...
├── src/
| ├── main.js
| ├── app.vue
├── node_modules/
| ├── ...
├── index.html
├── package.json
4. 添加 webpack 配置文件
在项目根目录下添加 webpack.config.js
文件。内容如下:
const path = require('path');
const VueLoaderPlugin = require('vue-loader/lib/plugin');
module.exports = {
entry: path.join(__dirname, 'src/main.js'), // 项目总入口js文件
// 输出文件
output: {
path: path.join(__dirname, 'dist'), // 所有的文件都输出到dist/目录下
filename: 'bundle.js'
},
module: {
rules: [{
// 使用vue-loader解析.vue文件
test: /\.vue$/,
loader: 'vue-loader'
},
{
test: /\.css$/,
// 要加上style-loader才能正确解析.vue文件里的<style>标签内容
use: ['style-loader', 'css-loader']
}
]
},
plugins: [
new VueLoaderPlugin() // 最新版的vue-loader需要配置插件
]
};
5. 添加构建脚本
在 package.json
文件的 scripts
属性里添加 build
脚本
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
+ "build": "webpack --config webpack.config.js"
},
因为我们添加了一些引用,比如 style-loader
、css-loader
等,所以也要安装相应的包。
npm i style-loader css-loader vue-template-compiler --save-dev
安装好依赖后运行 npm run build
来构建项目 或者可以继续往下。
6. 添加图片、CSS 预处理器等 loader
图片 loader 用的是 url-loader,它依赖于 file-loader,比 file-loader 多了一个可以比小于一定大小的图片直接转化成 base64 的形式插入到 html 页面,可以减少网络请求。
CSS 预处理器 我选的是 SASS。
安装依赖:
npm i file-loader url-loader node-sass sass-loader --save-dev
修改 webpack.config.js
module.exports =
{
test: /\.css$/,
// 要加上style-loader才能正确解析.vue文件里的<style>标签内容
use: ['style-loader', 'css-loader']
},
{
test: /\.scss$/,
use: [
// 处理顺序是从sass-loader到style-loader
'style-loader',
'css-loader',
'sass-loader'
]
},
{
test: /\.(gif|jpg|jpeg|png|svg)$/i,
use: [{
loader: 'url-loader',
options: {
// 当文件大小小于limit byte时会把图片转换为base64编码的dataurl,否则返回普通的图片
limit: 8192,
name: 'dist/assest/images/[name]-[hash:5].[ext]' // 图片文件名称加上内容哈希
}
}]
}
]
},
...
7. 添加 postcss-loader
+ autoprefixer
,自动添加 css
浏览器前缀
安装依赖:
npm i postcss-loader autoprefixer --save-dev
新增 postcss
配置文件 postcss.config.js
const autoprefixer = require('autoprefixer');
module.exports = {
plugins: [
autoprefixer({
browsers: ['last 5 versions']
})
]
};
修改 webpack.config.js
...
module: {
rules: [
...
{
test: /\.css$/,
use: [
// 要加上style-loader才能正确解析.vue文件里的<style>标签内容
'style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 1
}
},
'postcss-loader'
]
},
{
test: /\.scss$/,
use: [
// 处理顺序是从sass-loader到style-loader
'style-loader',
'css-loader',
{
loader: 'postcss-loader',
options: {
sourceMap: true
}
},
'sass-loader'
]
},
...
8. 添加 babel-loader
,转译 es6
代码为 es5
代码
添加.babelrc文件
{
"presets": [
"env"
],
"plugins": [
"transform-vue-jsx"
]
}
安装依赖:npm i babel-loader@7 babel-core babel-preset-env --save-dev
修改 webpack.config.js,module.rules 再加一条:
{
test: /\.js$/,
exclude: /(node_modules|bower_components)/, // 不处理这两个文件夹里的内容
loader: 'babel-loader'
}
9. 添加 html-webpack-plugin,自动生成 index.html 的内容
添加 HtmlWebpackPlugin,在 dist 文件夹也生成 index.html 页面,而且会自动加上对项目入口 js 文件的引用,也就是说我们自己写的 index.html 可以不用再手动指定 js 文件了,它会把 webpack 配置里的 entry 当中指定的 js 都插入到生成的 index.html 中,而且当输出的文件添加上 hash 值时也可以自动跟踪。
安装依赖
npm i html-webpack-plugin --save-dev
继续修改
const path = require('path');
const VueLoaderPlugin = require('vue-loader/lib/plugin');
+const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: path.join(__dirname, 'src/main.js'), // 项目总入口js文件
// 输出文件
output: {
path: path.join(__dirname, 'dist'),
- filename: 'bundle.js'
+ filename: 'bundle-[hash].js' // 输出文件的名称加上hash值
},
module: {
rules: [{
...
]
},
plugins: [
- new VueLoaderPlugin() // 最新版的vue-loader需要配置插件
+ new VueLoaderPlugin(), // 最新版的vue-loader需要配置插件
+ new HtmlWebpackPlugin({
+ filename: 'index.html', // 生成的文件名称
+ template: 'index.html', // 指定用index.html做模版
+ inject: 'body' // 指定插入的<script>标签在body底部
+ })
]
};
10. 添加 clean-webpack-plugin,每次 build 之前可以自动先清除输出文件夹
如果我们对输出的文件名称加上了哈希值的话,每次修改之后的哈希值都不一样,就是每次都生成了一个新的文件,那旧的那些其实就是多余的文件了(上面那张图的 dist 文件夹可以看出来)。因此我们可以用 clean-webpack-plugin 这个插件,在每次 build 之前先清除输出文件夹。
安装依赖
npm i clean-webpack-plugin --save-dev
修改 webpack.config.js,plugins 数组添加一项
const path = require('path');
const VueLoaderPlugin = require('vue-loader/lib/plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
+const CleanWebpackPlugin = require('clean-webpack-plugin');
module.exports = {
...
plugins: [
new VueLoaderPlugin(), // 最新版的vue-loader需要配置插件
new HtmlWebpackPlugin({
filename: 'index.html', // 生成的文件名称
template: 'index.html', // 指定用index.html做模版
inject: 'body' // 指定插入的<script>标签在body底部
}),
+ new CleanWebpackPlugin(['dist'])
]
};
11. 添加 webpack-dev-server,cross-env 配置更友好的开发环境
webpack-dev-server 可以在本地启动一个服务器,而且当有任何文件修改的时候会自动重新打包,并且刷新浏览器页面。此外 devServer 还有很多其他配置项,让我们可以更方便的开发。
安装依赖
npm i webpack-dev-server cross-env --save-dev
用上 cross-env 是因为我们接下来要修改 package.json 里的 scripts,而不同的平台写scripts 的方式不一样。
修改 package.json 里的 scripts
...
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
- "build": "webpack --config webpack.config.js"
+ "build": "cross-env NODE_ENV=production webpack --config webpack.config.js",
+ "dev": "cross-env NODE_ENV=development webpack-dev-server --config webpack.config.js"
},
....
12. 配置生产环境 css 单独分离打包,方便浏览器缓存+单独打包类库文件
因为类库文件是不用像业务代码一样经常更新的,单独打包可以让它们在浏览器里缓存,提高加载速度。
安装依赖
npm i mini-css-extract-plugin --save-dev
webpack.config.js 完整版本:
const path = require('path');
const VueLoaderPlugin = require('vue-loader/lib/plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const Webpack = require('webpack');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const devServerPort = 8000; // 开发服务器端口号
// 是否是开发环境
// process.env拿到的是一个对象,它的属性可以通过命令行参数传入
// 这个NODE_ENV就是从package.json的dev/build scripts传进来的
const isDev = process.env.NODE_ENV === 'development';
const config = {
// 指定webpack的模式,然后一些第三方库(比如vue、react等)也会针对这个值采用不同的包
// 像vue就有完整版、运行时版等。参考:https://vuejs.org/v2/guide/deployment.html#With-Build-Tools
mode: isDev ? "development" : "production",
entry: path.join(__dirname, 'src/main.js'), // 项目总入口js文件
// 输出文件
output: {
path: path.join(__dirname, 'dist'),
/**
* hash跟chunkhash的区别,如果entry有多个,或者需要单独打包类库到
* 一个js文件的时候,hash是所有打包出来的每个js文件都是同样的哈希,
* 而chunkhash就是只是那个chunk的哈希,也就是chunkhash如果那个chunk
* 没有变化就不会变,而hash只要某一个文件内容有变化都是不一样的,所以
* 用chunkhash区分开每一个文件有变化时才更新,让浏览器起到缓存的作用
*/
filename: isDev ? 'bundle.[hash:8].js' : '[name].[chunkhash:8].js',
},
module: {
rules: [{
// 使用vue-loader解析.vue文件
test: /\.vue$/,
loader: 'vue-loader'
},
{
test: /\.(gif|jpg|jpeg|png|svg)$/i,
use: [{
loader: 'url-loader',
options: {
// 当文件大小小于limit byte时会把图片转换为base64编码的dataurl,否则返回普通的图片
limit: 8192,
name: 'dist/assest/images/[name]-[hash:5].[ext]' // 图片文件名称加上内容哈希
}
}]
},
{
test: /\.js$/,
exclude: /(node_modules|bower_components)/, // 不处理这两个文件夹里的内容
loader: 'babel-loader'
}
]
},
plugins: [
new VueLoaderPlugin(), // 最新版的vue-loader需要配置插件
new HtmlWebpackPlugin({
filename: 'index.html', // 生成的文件名称
template: 'index.html', // 指定用index.html做模版
inject: 'body' // 指定插入的<script>标签在body底部
}),
new CleanWebpackPlugin(['dist'])
],
/**
* 添加可以自动解析的扩展
* 就是 import 的时候可以不用写后缀也能正确引用文件了
* eg:添加了'.vue',import App from './app.vue' 可以写成 import App from './app' 了
* 参考:https://webpack.docschina.org/configuration/resolve/#resolve-extensions
*/
resolve: {
extensions: ['.wasm', '.mjs', '.js', '.json', '.vue']
}
};
// 如果是开发环境
if (isDev) {
config.module.rules.push({
test: /\.css$/,
use: [
// 要加上style-loader才能正确解析.vue文件里的<style>标签内容
'style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 1
}
},
'postcss-loader'
]
}, {
test: /\.scss$/,
use: [
// 处理顺序是从sass-loader到style-loader
'style-loader',
'css-loader',
{
loader: 'postcss-loader',
options: {
sourceMap: true
}
},
'sass-loader'
]
});
// 指定开发环境启动的服务器的信息
config.devServer = {
// contentBase: path.join(__dirname, 'dist'),
port: devServerPort,
host: '0.0.0.0', // 配置成0.0.0.0的话通过ip,localhost都能访问
overlay: {
errors: true // 如果有编译错误的话直接显示到页面上
},
hot: true // 开启模块热替换【https://webpack.docschina.org/guides/hot-module-replacement】
};
config.plugins.push(
new Webpack.HotModuleReplacementPlugin() // 模块热替换插件
);
// 生成source map,方便调试
// https://webpack.docschina.org/configuration/devtool/#src/components/Sidebar/Sidebar.jsx
config.devtool = 'cheap-eval-source-map';
} else { // 生产环境
// 把css分离打包到单独的文件
config.module.rules.push({
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: {
importLoaders: 1
}
},
'postcss-loader'
]
}, {
test: /\.scss$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
{
loader: 'postcss-loader',
options: {
sourceMap: true
}
},
'sass-loader'
]
});
config.plugins.push(
new MiniCssExtractPlugin({
// Options similar to the same options in webpackOptions.output
// both options are optional
filename: "style.[hash:8].css",
chunkFilename: "[id].[hash:8].css"
})
);
// 单独打包第三方类库文件(比如vue框架)
config.optimization = {
splitChunks: {
chunks: 'async',
minSize: 30000,
maxSize: 0,
minChunks: 1,
maxAsyncRequests: 5,
maxInitialRequests: 3,
automaticNameDelimiter: '~',
name: true,
cacheGroups: {
commons: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all'
}
}
},
runtimeChunk: {
name: entrypoint => `runtimechunk~${entrypoint.name}`
}
};
}
module.exports = config;