本文学习用webpack4来构建一个vue项目。
1. 初始化项目
新建文件夹,控制台进入文件目录,执行:npm init
,一路回车,会生成一个package.json文件。
2. 全局安装webpack
全局安装:
npm install webpack webpack-cli vue -g
//如果是mac,需要以管理员身份运行: sudo npm install webpack webpack-cli vue -g
全局安装完成后,执行本地安装命令:
npm install webpack webpack-cli vue vue-loader webpack-dev-server
3. 安装依赖
安装常用依赖包:vue-template-compiler css-loader file-loader style-loader sass-loader url-loader html-webpack-plugin cross-env
npm install vue-template-compiler css-loader file-loader style-loader sass-loader url-loader html-webpack-plugin cross-env
vue-template-compiler
将vue2.0模板预编译为渲染函数(template => ast => render),以避免运行时编译开销和csp限制,与vue-loader一起使用。css-loader、style-loader
webpack打包只处理js之间的依赖关系,如果js中引入了css文件,就需要css-loader来识别这个模块。css文件经过css-loader处理后,会导出一个包含style样式的js数组,style-loader的作用是将这些样式内容挂载到html页面上,使其生效。html-webpack-plugin
最常用的插件,可为html文件中引入的外部资源如script、link动态添加每次更新后的hash;可生成html入口文件,比如单页面上可以生成一个html文件入口,配置N个html-webpack-plugin则可以生成N个页面入口。cross-env
跨平台设置和使用环境变量。
4. 编写webpack配置文件
在目录下创建一个webpack.config.js文件,编写配置:
const path = require('path');//nodejs中的基本包,处理文件路径
const VueLoaderPlugin = require('vue-loader/lib/plugin');//vue-loader需要配合此插件使用
const HtmlWebpackPlugin = require('html-webpack-plugin');
const config = {
target: 'web',
entry:path.join(__dirname, 'src/index.js'),
output: {
filename: 'bundle.js',
path: path.join(__dirname,'dist')
},
module: {
rules: [
{
test: /\.vue$/,
use: 'vue-loader'
},
{
test: /\.(gif|jpg|jpeg|png|svg)/,
use: [
{
loader: 'url-loader',
options: {
limit: 1024,
name: '[name].[ext]'
}
}
]
}
]
},
plugins: [
new VueLoaderPlugin(),
new HtmlWebpackPlugin()
]
}
module.exports = config
5. 区分开发环境和生产环境
package.json中增加两行配置:
"build": "cross-env NODE_ENV=production webpack --config webpack.config.js",//生产环境
"dev": "cross-env NODE_ENV=development webpack-dev-server --config webpack.config.js",//开发环境
{
"name": "vuetest",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "cross-env NODE_ENV=production webpack --config webpack.config.js",
"dev": "cross-env NODE_ENV=development webpack-dev-server --config webpack.config.js"
},
"author": "",
"license": "ISC",
"dependencies": {
"cross-env": "^7.0.2",
"css-loader": "^4.2.0",
"extract-text-webpack-plugin": "^3.0.2",
"file-loader": "^6.0.0",
"html-webpack-plugin": "^4.3.0",
"mini-css-extract-plugin": "^0.9.0",
"postcss-loader": "^3.0.0",
"sass-loader": "^9.0.2",
"style-loader": "^1.2.1",
"url-loader": "^4.1.0",
"vue": "^2.6.11",
"vue-loader": "^15.9.3",
"vue-template-compiler": "^2.6.11",
"webpack": "^4.44.1",
"webpack-cli": "^3.3.12"
}
}
webpack.config.js:
const path = require('path');//nodejs中的基本包,处理文件路径
const VueLoaderPlugin = require('vue-loader/lib/plugin');//vue-loader需要配合此插件使用
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack');
const isDev = process.env.NODE_ENV === 'development'
const config = {
target: 'web',
entry:path.join(__dirname, 'src/index.js'),
output: {
filename: 'bundle.js',
path: path.join(__dirname,'dist')
},
module: {
rules: [
{
test: /\.vue$/,
use: 'vue-loader'
},
{
test: /\.(gif|jpg|jpeg|png|svg)/,
use: [
{
loader: 'url-loader',
options: {
limit: 1024,
name: '[name].[ext]'
}
}
]
}
]
},
plugins: [
new VueLoaderPlugin(),
new HtmlWebpackPlugin(),
//通过配置了DefinePlugin,那么这里面的标识就相当于全局变量,便可以在src目录下直接取到当前环境变量。
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: isDev ? '"development"' : '"production"'
}
})
]
}
//开发环境
if(isDev){
config.devtool = '#cheap-module-eval-source-map' // 调试代码时可以看到自己原本的代码,而不是编译后的
config.devServer = {
port: 8000,
host: '0.0.0.0',
overlay: {
errors: true // 将webpack编译的错误显示在网页上面
},
open: true // 在启用webpack-dev-server时,自动打开浏览器
}
config.plugins.push(
new webpack.HotModuleReplacementPlugin(),//模块热替换
new webpack.NoEmitOnErrorsPlugin()
)
}
module.exports = config
6. 编写源文件
新建src目录,在src目录下新建app.vue和index.js。
app.vue:
<template>
<div>{{text}}</div>
</template>
<script>
export default {
data() {
return {
text: 'abc'
}
}
}
</script>
<style>
</style>
index.js:
import Vue from 'vue'
import App from './app.vue'
const root = document.createElement('div');
document.body.appendChild(root);
new Vue({
render: (h) => h(App)
}).$mount(root)
尝试执行npm run build
和npm run dev
查看效果,webpack会自动生成dist目录,将业务代码和类库代码打包成dist目录下的bundle.js文件。
7. 区分开发环境和生产环境的打包配置,在生产环境下,用mini-css-extract-plugin来分离css
通常,webpack将css内容都打包在bundle.js里,插件mini-css-extract-plugin能够将css和js模块分开打包,把css代码从js文件中抽离出来,单独出一个模块。
执行npm install mini-css-extract-plugin
修改webpack.config.js:
const path = require('path');//nodejs中的基本包,处理文件路径
const VueLoaderPlugin = require('vue-loader/lib/plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack');
const miniCssExtractPlugin = require('mini-css-extract-plugin');
const isDev = process.env.NODE_ENV === 'development'
const config = {
target: 'web',
entry:path.join(__dirname, 'src/index.js'),
output: {
filename: 'bundle.js',
path: path.join(__dirname,'dist')
},
module: {
rules: [
{
test: /\.vue$/,
use: 'vue-loader'
},
{
test: /\.(gif|jpg|jpeg|png|svg)/,
use: [
{
loader: 'url-loader',
options: {
limit: 1024,
name: '[name].[ext]'
}
}
]
}
]
},
// process.env.NODE_ENV = develpment
plugins: [
new VueLoaderPlugin(),
new HtmlWebpackPlugin(),
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: isDev ? '"development"' : '"production"'
}
})
]
}
//判断正式环境和开发环境
if (isDev) {
config.module.rules.push(
{
test: /\.css$/,
use: [
'style-loader',
'css-loader'
]
},
{
test: /\.scss$/,
use: [
'style-loader',
'css-loader',
'sass-loader'
]
},
)
config.devtool = '#cheap-module-eval-source-map'
config.devServer = {
port: 8000,
host: '0.0.0.0',
overlay: {
errors: true,
},
hot: true
}
config.plugins.push(
new webpack.HotModuleReplacementPlugin(),
new webpack.NoEmitOnErrorsPlugin()
)
} else {
config.module.rules.push(
{
test: /\.css$/,
use: [
miniCssExtractPlugin.loader,//注意:miniCssExtractPlugin与style-loader冲突,使用miniCssExtractPlugin时不要使用style-loader,否则会报错
// 'style-loader',
'css-loader'
]
},
{
test: /\.scss$/,
use: [
miniCssExtractPlugin.loader,//注意:miniCssExtractPlugin与style-loader冲突,使用miniCssExtractPlugin时不要使用style-loader,否则会报错
// 'style-loader',
'css-loader',
'sass-loader'
]
}
)
config.plugins.push(
new miniCssExtractPlugin({
filename: 'css/[name].[contenthash].css',
allChunks: true
}),
)
}
module.exports = config
8. 区分打包类库代码
bundle.js包含了类库代码和业务代码,每次更新业务代码,都生成新的bundle.js,意味着浏览器中缓存的bundle.js也得更新。
我们期望类库代码能够长时间地在浏览器中缓存,而不必随着业务代码的更新而更新。
使用webpack.optimization.splitChunks分割类库代码,webpack4中可直接使用,无需安装任何插件,修改后的配置文件如下:
const path = require('path');//nodejs中的基本包,处理文件路径
const VueLoaderPlugin = require('vue-loader/lib/plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack');
const miniCssExtractPlugin = require('mini-css-extract-plugin');
const isDev = process.env.NODE_ENV === 'development'
const config = {
target: 'web',
entry:path.join(__dirname, 'src/index.js'),
output: {
filename: 'bundle.js',
path: path.join(__dirname,'dist')
},
module: {
rules: [
{
test: /\.vue$/,
use: 'vue-loader'
},
{
test: /\.(gif|jpg|jpeg|png|svg)/,
use: [
{
loader: 'url-loader',
options: {
limit: 1024,
name: '[name].[ext]'
}
}
]
}
]
},
// process.env.NODE_ENV = develpment
plugins: [
new VueLoaderPlugin(),
new HtmlWebpackPlugin(),
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: isDev ? '"development"' : '"production"'
}
})
]
}
//判断正式环境和开发环境
if (isDev) {
config.module.rules.push(
{
test: /\.css$/,
use: [
'style-loader',
'css-loader'
]
},
{
test: /\.scss$/,
use: [
'style-loader',
'css-loader',
'sass-loader'
]
},
)
config.devtool = '#cheap-module-eval-source-map'
config.devServer = {
port: 8000,
host: '0.0.0.0',
overlay: {
errors: true,
},
hot: true
}
config.plugins.push(
new webpack.HotModuleReplacementPlugin(),
new webpack.NoEmitOnErrorsPlugin()
)
} else {
config.entry = {
app:path.join(__dirname, 'src/index.js')
}
config.optimization = {
splitChunks : {
chunks: 'all',
minSize: 30000,
maxSize: 0,
minChunks: 1,
maxAsyncRequests: 5,
maxInitialRequests: 3,
automaticNameDelimiter: '~',
name: true,
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true
}
}
}
},
config.output.filename = '[name].[chunkhash:8].js'
config.module.rules.push(
{
test: /\.css$/,
use: [
miniCssExtractPlugin.loader,//注意:miniCssExtractPlugin与style-loader冲突,使用miniCssExtractPlugin时不要使用style-loader,否则会报错
//'style-loader',
'css-loader'
]
},
{
test: /\.scss$/,
use: [
miniCssExtractPlugin.loader,//注意:miniCssExtractPlugin与style-loader冲突,使用miniCssExtractPlugin时不要使用style-loader,否则会报错
// 'style-loader',
'css-loader',
'sass-loader'
]
}
)
config.plugins.push(
new miniCssExtractPlugin({
filename: 'css/[name].[contenthash].css',
allChunks: true
}),
)
}
module.exports = config
执行npm run build
,dist目录下将生成业务代码app.js文件和类库代码vendors.js。
多次打包,会发现dist目录下生成了多个hash值不同的vendor.js和app.js,这时我们需要安装插件,在每次打包前清除dist目录。
npm install clean-webpack-plugin
webpack.config.js :
const path = require('path');//nodejs中的基本包,处理文件路径
const VueLoaderPlugin = require('vue-loader/lib/plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack');
const miniCssExtractPlugin = require('mini-css-extract-plugin');
// 引入打包时清除 dist 目录的插件,引入时需要用对象{ CleanWebpackPlugin }包裹起来
const {CleanWebpackPlugin} = require('clean-webpack-plugin');
const isDev = process.env.NODE_ENV === 'development'
const config = {
target: 'web',
entry:path.join(__dirname, 'src/index.js'),
output: {
filename: 'bundle.js',
path: path.join(__dirname,'dist')
},
resolve: {
alias: {
'@': path.resolve(__dirname,'./src'),
}
},
module: {
rules: [
{
test: /\.vue$/,
use: 'vue-loader'
},
{
test: /\.(gif|jpg|jpeg|png|svg)/,
use: [
{
loader: 'url-loader',
options: {
limit: 1024,
name: '[name].[ext]'
}
}
]
}
]
},
// process.env.NODE_ENV = develpment
plugins: [
new VueLoaderPlugin(),
new HtmlWebpackPlugin(),
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: isDev ? '"development"' : '"production"'
}
})
]
}
//判断正式环境和开发环境
if (isDev) {
config.module.rules.push(
{
test: /\.css$/,
use: [
'style-loader',
'css-loader'
]
},
{
test: /\.scss$/,
use: [
'style-loader',
'css-loader',
'sass-loader'
]
},
)
config.devtool = '#cheap-module-eval-source-map'
config.devServer = {
port: 8000,
host: '0.0.0.0',
overlay: {
errors: true,
},
hot: true
}
config.plugins.push(
new webpack.HotModuleReplacementPlugin(),
new webpack.NoEmitOnErrorsPlugin()
)
} else {
config.entry = {
app:path.join(__dirname, 'src/index.js')
}
config.optimization = {
splitChunks : {
chunks: 'all',
minSize: 30000,
maxSize: 0,
minChunks: 1,
maxAsyncRequests: 5,
maxInitialRequests: 3,
automaticNameDelimiter: '~',
name: true,
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true
}
}
}
},
config.output.filename = '[name].[chunkhash:8].js'
config.module.rules.push(
{
test: /\.css$/,
use: [
miniCssExtractPlugin.loader,
//'style-loader',
'css-loader'
]
},
{
test: /\.scss$/,
use: [
miniCssExtractPlugin.loader,
//'style-loader',
'css-loader',
'sass-loader'
]
}
)
config.plugins.push(
new miniCssExtractPlugin({
filename: 'css/[name].[contenthash].css',
allChunks: true
}),
// 打包时,把 dist 目录下的文件内容先清除
new CleanWebpackPlugin()
)
}
module.exports = config
9. 用babel将es6转译为浏览器能够识别的代码
安装相应的包:
npm install -D babel-loader @babel/core @babel/preset-env webpack
创建一个与webpack.config.js同级的文件.babelrc,写入内容:
{
"presets" : [
"@babel/preset-env"
]
}
webpack.config.js:
const path = require('path');//nodejs中的基本包,处理文件路径
const VueLoaderPlugin = require('vue-loader/lib/plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack');
const miniCssExtractPlugin = require('mini-css-extract-plugin');
// 引入打包时清除 dist 目录的插件,引入时需要用对象{ CleanWebpackPlugin }包裹起来
const {CleanWebpackPlugin} = require('clean-webpack-plugin');
const isDev = process.env.NODE_ENV === 'development'
const config = {
target: 'web',
entry:path.join(__dirname, 'src/index.js'),
output: {
filename: 'bundle.js',
path: path.join(__dirname,'dist')
},
resolve: {
alias: {
'@': path.resolve(__dirname,'./src'),
}
},
module: {
rules: [
{
test: /\.vue$/,
use: 'vue-loader'
},
{
test: /\.(gif|jpg|jpeg|png|svg)/,
use: [
{
loader: 'url-loader',
options: {
limit: 1024,
name: '[name].[ext]'
}
}
]
},
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader"
}
]
},
// process.env.NODE_ENV = develpment
plugins: [
new VueLoaderPlugin(),
new HtmlWebpackPlugin(),
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: isDev ? '"development"' : '"production"'
}
})
]
}
//判断正式环境和开发环境
if (isDev) {
config.module.rules.push(
{
test: /\.css$/,
use: [
'style-loader',
'css-loader'
]
},
{
test: /\.scss$/,
use: [
'style-loader',
'css-loader',
'sass-loader'
]
},
)
config.devtool = '#cheap-module-eval-source-map'
config.devServer = {
port: 8000,
host: '0.0.0.0',
overlay: {
errors: true,
},
hot: true
}
config.plugins.push(
new webpack.HotModuleReplacementPlugin(),
new webpack.NoEmitOnErrorsPlugin()
)
} else {
config.entry = {
app:path.join(__dirname, 'src/index.js')
}
config.optimization = {
splitChunks : {
chunks: 'all',
minSize: 30000,
maxSize: 0,
minChunks: 1,
maxAsyncRequests: 5,
maxInitialRequests: 3,
automaticNameDelimiter: '~',
name: true,
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true
}
}
}
},
config.output.filename = '[name].[chunkhash:8].js'
config.module.rules.push(
{
test: /\.css$/,
use: [
miniCssExtractPlugin.loader,
//'style-loader',
'css-loader'
]
},
{
test: /\.scss$/,
use: [
miniCssExtractPlugin.loader,
//'style-loader',
'css-loader',
'sass-loader'
]
}
)
config.plugins.push(
new miniCssExtractPlugin({
filename: 'css/[name].[contenthash].css',
allChunks: true
}),
// 打包时,把 dist 目录下的文件内容先清除
new CleanWebpackPlugin()
)
}
module.exports = config