前言
从脚手架开始学前端 【第5期】Vue脚手架搭建中,我们已经学到了如何使用Vue官网提供的vue-cli可以快速搭建一个项目,但是对于其中的原理方面我们还不是很清楚,今天我们就动手搭建自己vue开发环境,今天我们使用 webpack4来一步一步搭建自己的vue开发环境,如果还对webpack不熟悉的小伙伴,可以直接到官网传送门
准备
- 安装node环境
- 安装npm包管理
- 安装yarn
配置内容
- ES6/7/8/……代码转换成ES5代码
- scss/sass/less/stylus转css
- .vue文件转换成js文件
- 使用 jpg、png,font等资源文件
- 自动添加css各浏览器产商的前缀
- 代码热更新
- 资源预加载
- 每次构建代码清除之前生成的代码
- 定义环境变量
- 区分开发环境打包跟生产环境打包
搭建流程
一、创建项目
创建项目目录并初始化项目
$ mkdir vue-cli-demo && cd vue-cli-demo && yarn init -y
打开package.json
{
"name": "vue-cli-demo",
"version": "1.0.0",
"main": "index.js",
"repository": "https://github.com/xunzhaotech/vue-cli-demo.git",
"author": "xunzhaotech
"license": "MIT"
}
二、安装
在webpack4后,webpack-cli从webpack包里面分离出来了,所以需要自己安装啦!
2.1安装webpack(推荐局部安装)
// 全局卸载
$ yarn global remove webpack
// 局部卸载
$ yarn remove webpack
// 全局安装
$ yarn add webpack -g //-g 代表全局安装
// 局部安装
$ yarn add webpack -S //-S是-save的缩写
$ yarn add webpack -D //-D 是--save-dev的缩写
$ yarn add webpack-cli -D //-D 是--save-dev的缩写
// 简化安装
$ yarn add webpack webpack-cli -D
webpack 建议在局部安装,如果安装到全局,会锁定webpack版本,多项目使用 webpack 会因为版本问题导致项目构建失败
./node_modules/.bin/webpack -v
安装完了检查版本,确认安装成功
2.2测试webpack是否安装成功(webpack 默认配置中 entry 入口是 src/index.js)
-
创建入口文件 ./src/main.js
$ mkdir src && touch ./src/main.js
src/main.js
/*
* @Author: your name
* @Date: 2020-03-18 14:25:09
* @LastEditTime: 2020-03-18 14:25:29
* @LastEditors: Please set LastEditors
* @Description: In User Settings Edit
* @FilePath: \vue-cli-demo\src\main.js
*/
console.log('Hello vue-cli-demo')
这个时候到./node_modules/.bin/目录下执行webpack打包命令,我们会看到dist目录下生成对应的js文件。 这种打包方式比较麻烦,这里添加一个配置就可以更方便打包。在 package.json 文件下的 script 节点添加一项配置 "serve": "webpack",然后再删掉 dist 目录,再运行 yarn run serve就可以方便地打包了。
-
然后在 package.json 下面加一个脚本命令
"scripts": {
"serve": "webpack ./src/main.js --mode development",
"dev": "webpack"(默认为src/index.js)
},
-
然后运行该命令
$ yarn run serve
或
$ yarn serve
如果在 dist 目录下生成了一个main.js文件,则表示webpack工作正常
-
创建webpack配置文件
注意:windows不支持此类创建,具体自行百度或者手动创建或者copy
$ touch webpack.config.js
webpack.config.js配置
// 配置入口文件与输出打包文件及路径
const path = require('path')
module.exports = {
entry: './src/index.js',
output: {
path: path.join(__dirname, 'dist'),
filename: 'index.js'
}
}
-
配置webpack编译脚本
"scripts": {
"dev": "webpack",
"build": "webpack --config webpack.config.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
-
测试配置
$ yarn build && node ./dist/bundle.js
// 输出表示打包成功
Hello vue-cli-demo
三、配置
- 新建一个 build 文件夹,用来存放 webpack配置相关的文件
- 在build文件夹下新建一个webpack.base.conf.js,配置webpack的基本配置
- 在build文件夹下新建一个webpack.dev.conf.js,配置webpack的开发环境配置
- 在build文件夹下新建一个webpack.prod.conf.js,配置webpack的生产环境配置
- 修改 webpack.base.conf.js配置
/*
* @Author: your name
* @Date: 2020-03-18 09:54:47
* @LastEditTime: 2020-03-29 13:50:31
* @LastEditors: Please set LastEditors
* @Description: In User Settings Edit
* @FilePath: \doc_exchange_front_end\build\webpack.base.conf.js
*/
'use strict'
const path = require('path')
module.exports = {
// 打包模式
mode: 'development',
entry: {
// 配置入口文件
main: path.resolve(__dirname, '../src/main.js')
},
output: {
// 配置打包文件输出的目录
path: path.resolve(__dirname, '../dist'),
// 生成的 js 文件名称
filename: 'js/[name].[hash:8].js',
// 生成的 chunk 名称
chunkFilename: 'js/[name].[hash:8].js',
// 资源引用的路径
publicPath: '/'
}
}
- 修改package.json 文件
"scripts": {
"serve": "webpack ./src/main.js --config ./build/webpack.base.conf.js"
},
- 测试是否配置成功
$ yarn run serve
或
$ yarn serve
注意:查看dist文件夹下生成js文件夹和带有hash命名的main文件表示配置成功。
3.1 loader配置
所谓loaders就是说把原本webpack不支持加载的文件或者文件内容通过loaders进行加载解析,实现应用的目的。loader是一种打包的方案,webpack默认只识别js结尾的文件,当遇到其他格式的文件后,webpack并不知道如何去处理。此时,我们可以定义一种规则,告诉webpack当他遇到某种格式的文件后,处理的方案就是所说的loader。webpack 默认只支持 js/json,可以使用 loader 来预处理文件。这允许你打包除 JavaScript 之外的任何静态资源。你可以使用 Node.js 来很简单地编写自己的 loader。
3.1.1 配置 ES6/7/8 转 ES5代码
这里讲解ES6/7/8解析,原生支持js解析,但是不能解析ES6/7/8,需要babel-loader ,而babel-loader 又依赖babel。babel还可以解析jsx语法。
- 安装babel相关依赖
ES6/7/8转换为ES5需要安装babel,babel-loader @babel/core @babel/preset-env
$ yarn add babel-loader @babel/core @babel/preset-env -S
- 修改 webpack.base.conf.js配置
/*
* @Author: your name
* @Date: 2020-03-18 09:54:47
* @LastEditTime: 2020-03-29 14:33:55
* @LastEditors: Please set LastEditors
* @Description: In User Settings Edit
* @FilePath: \doc_exchange_front_end\build\webpack.base.conf.js
*/
'use strict'
const path = require('path')
module.exports = {
// 打包模式
mode: 'development',
entry: {
// 配置入口文件
main: path.resolve(__dirname, '../src/main.js')
},
output: {
// 配置打包文件输出的目录
path: path.resolve(__dirname, '../dist'),
// 生成的 js 文件名称
filename: 'js/[name].[hash:8].js',
// 生成的 chunk 名称
chunkFilename: 'js/[name].[hash:8].js',
// 资源引用的路径
publicPath: '/'
},
module: {
rules: [
{
test: /\.jsx$/,
exclude: /node_modules/,
use: [
{
loader: 'babel-loader'
}
]
}
]
}
}
- 在项目根目录添加一个 babel.config.js 文件或者在根目录创建 .babelrc 文件,输入以下内容
babel.config.js内容
/*
* @Author: your name
* @Date: 2020-03-29 14:37:48
* @LastEditTime: 2020-03-29 14:39:50
* @LastEditors: your name
* @Description: In User Settings Edit
* @FilePath: \vue-cli-demo\babel.config.js
*/
module.exports = {
presets: [
[
"@babel/preset-env"
]
]
}
.babelrc 文件内容
{
"presets": [
"@babel/preset-env",
]
}
接着在 webpack.base.conf.js配置文件下module属性,属性内容是一个rules集合中添加如下内容
// ...
module: {
rules: [
{
test: /.js$/,
use: 'babel-loader'
},
]
}
// ...
rules集合的每个元素都是一个文件类型的配置信息,这里只有一个js文件,后面会讲到css、less及各种格式的图片等;test是一个正则,用来匹配文件后缀名;use表示此loader名称。
-
然后执行 yarn run serve或者yarn serve 命令,可以看到 ES6代码被转成了ES5代码了
-
注意
babel-loader只会将 ES6/7/8语法转换为ES5语法,但是对新api并不会转换。 我们可以通过安装 babel-polyfill 提供新语法的实现
安装
$ yarn add @babel/polyfill -S
修改 webpack.base.conf.js配置
/*
* @Author: your name
* @Date: 2020-03-18 09:54:47
* @LastEditTime: 2020-03-29 14:56:27
* @LastEditors: Please set LastEditors
* @Description: In User Settings Edit
* @FilePath: \doc_exchange_front_end\build\webpack.base.conf.js
*/
'use strict'
const path = require('path')
module.exports = {
// 打包模式
mode: 'development',
entry: {
// 配置入口文件
main: ["@babel/polyfill",path.resolve(__dirname, '../src/main.js')]
},
output: {
……
},
module: {
rules: [
{
test: /\.jsx$/,
exclude: /node_modules/,
use: [
{
loader: 'babel-loader'
}
]
}
]
}
}
- 按需引入polyfill
配置了按需引入 polyfill 后,用到es6以上的函数,babel会自动导入相关的polyfill,这样能大大减少 打包编译后的体积
安装依赖
$ yarn add core-js@2 @babel/runtime-corejs2 -S
修改 babel-config.js
/*
* @Author: your name
* @Date: 2020-03-29 14:37:48
* @LastEditTime: 2020-03-29 14:39:50
* @LastEditors: your name
* @Description: In User Settings Edit
* @FilePath: \vue-cli-demo\babel.config.js
*/
module.exports = {
presets: [
[
"@babel/preset-env",
{
useBuiltIns: "usage"
}
]
]
}
3.1.2 配置scss/sass/less/stylus转css
在没有引入loader前,引入scss/sass/less/stylus打包会报错
- scss/sass转css配置
sass-loader, dart-sass主要是将 scss/sass 语法转为css,css-loader主要是解析 css 文件 ,style-loader 主要是将 css 解析到 html页面 的 style 上
安装相关依赖
$ yarn add sass-loader dart-sass css-loader style-loader -D
修改 webpack.base.conf.js配置
- less转css配置
css-loader用于加载css文件并生成commonjs对象,style-loader用于将样式通过style标签插入到head
安装loader
$ yarn add style-loader css-loader less less-loader -D
修改 webpack.base.conf.js配置
// ...
module: {
rules: [
{
test: /.js$/,
use: 'babel-loader'
},
{
test: /.css$/,
use: [
'style-loader',
'css-loader'
]
},
{
test: /.less$/,
use: [
'style-loader',
'css-loader',
'less-loader'
]
},
]
}
// ...
注意:这里的解析css用到了两个loader,所以use对象里是个数组,需要格外注意到loader顺序,是先写style-loader,再写css-loader,但是执行的时候是先加载css-loader,将css解析好后再将css传递给style-loader;
- stylus转css配置
3.1.3 配置 postcss 实现自动添加css3前缀
- 安装相关依赖
$ yarn add postcss-loader autoprefixer -D
- 修改 webpack.base.conf.js配置
……
module: {
rules: [
{
test: /\.jsx$/,
exclude: /node_modules/,
use: [
{
loader: 'babel-loader'
}
]
},
{
test: /\.(scss|sass)$/,
use: [
{
loader: 'style-loader'
},
{
loader: 'css-loader',
options: {
importLoaders: 2
}
},
{
loader: 'sass-loader',
options: {
implementation: require('dart-sass')
}
},
{
loader: 'postcss-loader'
}
]
}
]
}
……
- 在项目根目录下新建一个 postcss.config.js
/*
* @Author: your name
* @Date: 2020-03-29 15:39:27
* @LastEditTime: 2020-03-29 15:39:43
* @LastEditors: your name
* @Description: In User Settings Edit
* @FilePath: \vue-cli-demo\postcss.config.js
*/
module.exports = {
plugins: {
autoprefixer: {}
}
}
3.1.4 配置 webpack 打包 图片、媒体、字体等文件
ebpack 打包 图片、媒体、字体等文件在webpack中的打包步骤跟上面类似,只不过loader不同。
- 安装依赖
file-loader 解析文件url,并将文件复制到输出的目录中 url-loader 功能与 file-loader 类似,如果文件小于限制的大小。则会返回 base64 编码,否则使用 file-loader 将文件复制到输出的目录中,url-loader直接内置了file-loader,是对它的再封装,在配置文件里可以直接去掉file,用url替换。
$ yarn add file-loader url-loader -D
- 修改 webpack.base.conf.js配置 添加 rules 配置,分别对 图片,媒体,字体文件进行配置
//..
module: {
rules: [
{
test: /\.jsx$/,
exclude: /node_modules/,
use: [
{
loader: 'babel-loader'
}
]
},
{
test: /\.(scss|sass)$/,
use: [
{
loader: 'style-loader'
},
{
loader: 'css-loader',
options: {
importLoaders: 2
}
},
{
loader: 'sass-loader',
options: {
implementation: require('dart-sass')
}
},
{
loader: 'postcss-loader'
}
]
},
{
test: /\.(jpe?g|png|gif|jpeg)$/i,
use: [
{
loader: 'url-loader',
options: {
limit: 4096,
fallback: {
loader: 'file-loader',
options: {
name: 'img/[name].[hash:8].[ext]'
}
}
}
}
]
},
{
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
use: [
{
loader: 'url-loader',
options: {
limit: 4096,
fallback: {
loader: 'file-loader',
options: {
name: 'media/[name].[hash:8].[ext]'
}
}
}
}
]
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/i,
use: [
{
loader: 'url-loader',
options: {
limit: 4096,
fallback: {
loader: 'file-loader',
options: {
name: 'fonts/[name].[hash:8].[ext]'
}
}
}
}
]
},
]
},
//..
注意:limit是指图片大小上限,单位是字节,如果图片大小小于这个值,就会被打包为base64格式,否则就仍是图片。
3.1.5 webpack 编译 .vue 文件
浏览器只能识别html/js/css,对于.vue文件直接引入,浏览器会报错,这个时候webpack的vue-loader就派上了用场
- 安装需要的依赖文件
$ yarn add vue-loader vue-template-compiler cache-loader thread-loader -D
$ yarn add vue -S
- vue-loader 用于解析.vue文件
- vue-template-compiler 用于编译模板
- cache-loader 用于缓存loader编译的结果
- thread-loader 使用 worker 池来运行loader,每个 worker 都是一个 node.js 进程。
- 修改 webpack.base.conf.js配置
/*
* @Author: your name
* @Date: 2020-03-18 09:54:47
* @LastEditTime: 2020-03-29 22:20:37
* @LastEditors: Please set LastEditors
* @Description: In User Settings Edit
* @FilePath: \doc_exchange_front_end\build\webpack.base.conf.js
*/
'use strict'
const path = require('path')
// 引入生成页面插件
const HtmlWebpackPlugin = require('html-webpack-plugin')
// 引入webpack
const webpack = require('webpack')
// 引入vueloader编译器
const VueLoaderPlugin = require('vue-loader/lib/plugin')
module.exports = {
// 打包模式
mode: 'development',
entry: {
// 配置入口文件
main: ["@babel/polyfill",path.resolve(__dirname, '../src/main.js')]
},
output: {
// 配置打包文件输出的目录
path: path.resolve(__dirname, '../dist'),
// 生成的 js 文件名称
filename: 'js/[name].[hash:8].js',
// 生成的 chunk 名称
chunkFilename: 'js/[name].[hash:8].js',
// 资源引用的路径
publicPath: '/'
},
resolve: {
alias: {
vue$: 'vue/dist/vue.runtime.esm.js'
},
},
module: {
rules: [
{
test: /\.vue$/,
use: [
{
loader: 'cache-loader'
},
{
loader: 'thread-loader'
},
{
loader: 'vue-loader',
options: {
compilerOptions: {
preserveWhitespace: false
},
}
}
]
},
{
test: /\.jsx?$/,
exclude: /node_modules/,
use: [
{
loader: 'cache-loader'
},
{
loader: 'thread-loader'
},
{
loader: 'babel-loader'
}
]
},
{
test: /\.(scss|sass)$/,
use: [
{
loader: 'style-loader'
},
{
loader: 'css-loader',
options: {
importLoaders: 2
}
},
{
loader: 'sass-loader',
options: {
implementation: require('dart-sass')
}
},
{
loader: 'postcss-loader'
}
]
},
{
test: /\.(jpe?g|png|gif|jpeg)$/i,
use: [
{
loader: 'url-loader',
options: {
limit: 4096,
fallback: {
loader: 'file-loader',
options: {
name: 'img/[name].[hash:8].[ext]'
}
}
}
}
]
},
{
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
use: [
{
loader: 'url-loader',
options: {
limit: 4096,
fallback: {
loader: 'file-loader',
options: {
name: 'media/[name].[hash:8].[ext]'
}
}
}
}
]
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/i,
use: [
{
loader: 'url-loader',
options: {
limit: 4096,
fallback: {
loader: 'file-loader',
options: {
name: 'fonts/[name].[hash:8].[ext]'
}
}
}
}
]
},
]
},
devServer: {
//……
},
plugins: [
//……
new VueLoaderPlugin()
]
}
3.2 插件配置(plugins)
插件是 webpack 的支柱功能。webpack 自身也是构建在 webpack 配置中用到的相同的插件系统之上。插件目的在于解决 loader 无法实现的其他事。webpack 有着丰富的插件接口(rich plugin interface)。webpack 自身的多数功能都使用这个插件接口。这个插件接口使 webpack 变得极其灵活。
3.2.1 插件html-webpack-plugin(必须)
使用 html-webpack-plugin来自动创建html页面,并将打包的bundle.js并自动引入
- 安装依赖
$ npm install html-webpack-plugin --save-dev
或
$ yarn add html-webpack-plugin -D
- 新建一个 public/index.html 页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<!-- <link rel="icon" href="<%= BASE_URL %>favicon.ico"> -->
<title>vue-cli-demo</title>
</head>
<body>
<noscript>
<strong>We're sorry but vue-admin doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
- 修改 webpack.base.conf.js配置
/*
* @Author: your name
* @Date: 2020-03-18 09:54:47
* @LastEditTime: 2020-03-29 16:03:52
* @LastEditors: Please set LastEditors
* @Description: In User Settings Edit
* @FilePath: \doc_exchange_front_end\build\webpack.base.conf.js
*/
'use strict'
const path = require('path')
// 引入生成页面插件
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
// 打包模式
mode: 'development',
entry: {
// 配置入口文件
main: ["@babel/polyfill",path.resolve(__dirname, '../src/main.js')]
},
output: {
……
},
module: {
……
},
plugins: [
new HtmlWebpackPlugin({
template: path.resolve(__dirname, '../public/index.html')
})
]
}
3.2.2 配置 clean-webpack-plugin
每次部署自动清空 dist 目录
- 安装依赖
$ npm install clean-webpack-plugin --save-dev
或
$ yarn add clean-webpack-plugin -D
- 修改 webpack.base.conf.js配置
/*
* @Author: your name
* @Date: 2020-03-18 09:54:47
* @LastEditTime: 2020-03-29 16:03:52
* @LastEditors: Please set LastEditors
* @Description: In User Settings Edit
* @FilePath: \doc_exchange_front_end\build\webpack.base.conf.js
*/
const htmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
plugins: [
// 复制一个 html 并将最后打包好的资源在 html 中引入
new htmlWebpackPlugin({
// 页面title 需要搭配 ejs 使用
title: "讯曌科技",
// html 模板路径
template: "./index.html",
// 输出文件名称
filename: "index.html",
minify: {
// 压缩HTML⽂件
removeComments: true, // 移除HTML中的注释
collapseWhitespace: true, // 删除空⽩符与换⾏符
minifyCSS: true // 压缩内联css
}
}),
// 每次部署时清空 dist 目录
new CleanWebpackPlugin()
],
3.2.3 配置 devServer 热更新功能
通过代码的热更新功能,我们可以实现不刷新页面的情况下,更新我们的页面,通过配置 devServer 和 HotModuleReplacementPlugin 插件来实现热更新
- 安装依赖
$ yarn add webpack-dev-server -D
- 修改 webpack.base.conf.js配置
/*
* @Author: your name
* @Date: 2020-03-18 09:54:47
* @LastEditTime: 2020-03-29 16:18:06
* @LastEditors: Please set LastEditors
* @Description: In User Settings Edit
* @FilePath: \doc_exchange_front_end\build\webpack.base.conf.js
*/
'use strict'
const path = require('path')
// 引入生成页面插件
const HtmlWebpackPlugin = require('html-webpack-plugin')
// 引入webpack
const webpack = require('webpack')
module.exports = {
// 打包模式
mode: 'development',
entry: {
// 配置入口文件
main: ["@babel/polyfill",path.resolve(__dirname, '../src/main.js')]
},
output: {
// 配置打包文件输出的目录
path: path.resolve(__dirname, '../dist'),
// 生成的 js 文件名称
filename: 'js/[name].[hash:8].js',
// 生成的 chunk 名称
chunkFilename: 'js/[name].[hash:8].js',
// 资源引用的路径
publicPath: '/'
},
module: {
rules: [
{
test: /\.jsx$/,
exclude: /node_modules/,
use: [
{
loader: 'babel-loader'
}
]
},
{
test: /\.(scss|sass)$/,
use: [
{
loader: 'style-loader'
},
{
loader: 'css-loader',
options: {
importLoaders: 2
}
},
{
loader: 'sass-loader',
options: {
implementation: require('dart-sass')
}
},
{
loader: 'postcss-loader'
}
]
}
]
},
devServer: {
hot: true,
port: 3000,
contentBase: './dist',
open: true,
},
plugins: [
new HtmlWebpackPlugin({
template: path.resolve(__dirname, '../public/index.html')
}),
new webpack.NamedModulesPlugin(),
new webpack.HotModuleReplacementPlugin(),
]
}