前言
从脚手架开始学前端 【第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(), ] }