面试官:自己搭建过vue开发环境吗?
开篇
前段时间,看到群里一些小伙伴面试的时候被面试官问到这类题目。平时大家开发vue项目的时候,相信大部分人都是使用 vue-cli
脚手架生成的项目架构,然后npm run install
安装依赖,npm run serve
启动项目然后就开始写业务代码了。
但是对项目里的webpack
封装和配置了解的不清楚,容易导致出问题不知如何解决,或者不会通过webpack
去扩展新功能。
该篇文章主要是想告诉小伙伴们,如何一步一步的通过 webpack4
来搭建自己的vue
开发环境
首先我们要知道 vue-cli
生成的项目,帮我们配置好了哪些功能?
-
es6
代码转换成es5
代码 -
scss/sass/less/stylus
转css
-
.vue
文件转换成js
文件 - 使用
jpg
、png
,font
等资源文件 - 自动添加css各浏览器产商的前缀
- 代码热更新
- 资源预加载
- 每次构建代码清除之前生成的代码
- 定义环境变量
- 区分开发环境打包跟生产环境打包
- ....
1. 搭建 webpack
基本环境
该篇文章并不会细讲 webpack
是什么东西,如果还不是很清楚的话,可以先去看看
简单的说,webpack
是一个模块打包机,可以分析你的项目依赖的模块以及一些浏览器不能直接运行的语言jsx
、vue
等转换成 js
、css
文件等,供浏览器使用。
1.1 初始化项目
在命令行中执行 npm init
然后一路回车就行了,主要是生成一些项目基本信息。最后会生成一个 package.json
文件
npm init
1.2 安装webpack
1.3 写点小代码测试一下webpack
是否安装成功了
新建一个src
文件夹,然后再建一个main.js
文件
// src/main.js console.log('hello webpack')
然后在 package.json 下面加一个脚本命令
然后运行该命令
npm run serve
如果在 dist 目录下生成了一个main.js
文件,则表示webpack
工作正常
2. 开始配置功能
- 新建一个
build
文件夹,用来存放webpack
配置相关的文件 - 在
build
文件夹下新建一个webpack.config.js
,配置webpack
的基本配置 - 修改
webpack.config.js
配置
- 修改
package.json
文件,将之前添加的serve
修改为
"serve": "webpack ./src/main.js --config ./build/webpack.config.js"
2.1 配置 es6/7/8
转 es5
代码
- 安装相关依赖
npm install babel-loader @babel/core @babel/preset-env
- 修改
webpack.config.js
配置
- 在项目根目录添加一个
babel.config.js
文件
- 然后执行
npm run serve
命令,可以看到 es6代码被转成了es5代码了
2.1.1 es6/7/8 api
转es5
babel-loader
只会将 es6/7/8语法转换为es5语法,但是对新api并不会转换。
我们可以通过 babel-polyfill 对一些不支持新语法的客户端提供新语法的实现
- 安装
npm install @babel/polyfill
- 修改
webpack.config.js
配置
在 entry
中添加 @babel-polyfill
2.1.2 按需引入polyfill
2.1.2 和 2.1.1 只需要配置一个就行
修改时间 2019-05-05、 来自评论区 兮漫天 的提醒
- 安装相关依赖
npm install core-js@2 @babel/runtime-corejs2 -s
- 修改 babel-config.js
配置了按需引入 polyfill
后,用到es6
以上的函数,babel
会自动导入相关的polyfill
,这样能大大减少 打包编译后的体积
2.2 配置 scss
转 css
在没配置 css
相关的 loader
时,引入scss
、css
相关文件打包的话,会报错
- 安装相关依赖
npm install sass-loader dart-sass css-loader style-loader -d
sass-loader
, dart-sass
主要是将 scss/sass 语法转为css
css-loader
主要是解析 css 文件
style-loader
主要是将 css 解析到 html
页面 的 style
上
- 修改
webpack.config.js
配置
2.3 配置 postcss 实现自动添加css3前缀
- 安装相关依赖
npm install postcss-loader autoprefixer -d
- 修改
webpack.config.js
配置
- 在项目根目录下新建一个
postcss.config.js
2.3 使用 html-webpack-plugin
来创建html页面
使用 html-webpack-plugin
来创建html页面,并自动引入打包生成的js
文件
- 安装依赖
npm install html-webpack-plugin -d
- 新建一个 public/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>document</title> </head> <body> <div id="app"></div> </body> </html>
- 修改
webpack-config.js
配置
2.4 配置 devserver 热更新功能
通过代码的热更新功能,我们可以实现不刷新页面的情况下,更新我们的页面
- 安装依赖
npm install webpack-dev-server -d
- 修改
webpack.config.js
配置
通过配置 devserver
和 hotmodulereplacementplugin
插件来实现热更新
2.5 配置 webpack 打包 图片、媒体、字体等文件
- 安装依赖
npm install file-loader url-loader -d
file-loader
解析文件url,并将文件复制到输出的目录中
url-loader
功能与 file-loader
类似,如果文件小于限制的大小。则会返回 base64
编码,否则使用 file-loader
将文件复制到输出的目录中
- 修改
webpack-config.js
配置
添加rules
配置,分别对 图片,媒体,字体文件进行配置
// build/webpack.config.js const path = require('path') const htmlwebpackplugin = require('html-webpack-plugin') const webpack = require('webpack') module.exports = { // 省略其它配置 ... module: { rules: [ // ... { test: /\.(jpe?g|png|gif)$/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]' } } } } ] }, ] }, plugins: [ // ... ] }
3. 让 webpack
识别 .vue
文件
- 安装需要的依赖文件
npm install vue-loader vue-template-compiler cache-loader thread-loader -d npm install vue -s
vue-loader
用于解析.vue
文件
vue-template-compiler
用于编译模板
cache-loader
用于缓存loader
编译的结果
thread-loader
使用 worker
池来运行loader
,每个 worker
都是一个 node.js
进程。
- 修改
webpack.config.js
配置
// build/webpack.config.js const path = require('path') const webpack = require('webpack') const htmlwebpackplugin = require('html-webpack-plugin') const vueloaderplugin = require('vue-loader/lib/plugin') module.exports = { // 指定打包模式 mode: 'development', entry: { // ... }, output: { // ... }, devserver: { // ... }, 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?$/, use: [ { loader: 'cache-loader' }, { loader: 'thread-loader' }, { loader: 'babel-loader' } ] }, // ... ] }, plugins: [ // ... new vueloaderplugin() ] }
- 测试一下
- 在 src 新建一个 app.vue
// src/app.vue <template> <div class="app"> hello world </div> </template> <script> export default { name: 'app', data() { return {}; } }; </script> <style lang="scss" scoped> .app { color: skyblue; } </style>
- 修改
main.js
import vue from 'vue' import app from './app.vue' new vue({ render: h => h(app) }).$mount('#app')
- 运行一下
npm run serve
4. 定义环境变量
通过 webpack
提供的defineplugin
插件,可以很方便的定义环境变量
plugins: [ new webpack.defineplugin({ 'process.env': { vue_app_base_url: json.stringify('http://localhost:3000') } }), ]
5. 区分生产环境和开发环境
新建两个文件
-
webpack.dev.js
开发环境使用 -
webpack.prod.js
生产环境使用 webpack.config.js
公用配置-
开发环境与生产环境的不同
5.1 开发环境
- 不需要压缩代码
- 需要热更新
- css不需要提取到css文件
- sourcemap
-
...
5.2 生产环境
- 压缩代码
- 不需要热更新
- 提取css,压缩css文件
- sourcemap
- 构建前清除上一次构建的内容
...
- 安装所需依赖
npm i @intervolga/optimize-cssnano-plugin mini-css-extract-plugin clean-webpack-plugin webpack-merge copy-webpack-plugin -d
-
@intervolga/optimize-cssnano-plugin
用于压缩css代码 -
mini-css-extract-plugin
用于提取css到文件中 -
clean-webpack-plugin
用于删除上次构建的文件 -
webpack-merge
合并webpack
配置 -
copy-webpack-plugin
用户拷贝静态资源
5.3 开发环境配置
- build/webpack.dev.js
// build/webpack.dev.js const merge = require('webpack-merge') const webpackconfig = require('./webpack.config') const webpack = require('webpack') module.exports = merge(webpackconfig, { mode: 'development', devtool: 'cheap-module-eval-source-map', module: { rules: [ { test: /\.(scss|sass)$/, use: [ { loader: 'style-loader' }, { loader: 'css-loader', options: { importloaders: 2 } }, { loader: 'sass-loader', options: { implementation: require('dart-sass') } }, { loader: 'postcss-loader' } ] }, ] }, plugins: [ new webpack.defineplugin({ 'process.env': { node_env: json.stringify('development') } }), ] })
- webpack.config.js
// build/webpack.config.js const path = require('path') const webpack = require('webpack') const htmlwebpackplugin = require('html-webpack-plugin') const vueloaderplugin = require('vue-loader/lib/plugin') module.exports = { 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: '/' }, devserver: { hot: true, port: 3000, contentbase: './dist' }, resolve: { alias: { vue$: 'vue/dist/vue.runtime.esm.js' }, extensions: [ '.js', '.vue' ] }, module: { rules: [ { test: /\.vue$/, use: [ { loader: 'cache-loader' }, { loader: 'vue-loader', options: { compileroptions: { preservewhitespace: false }, } } ] }, { test: /\.jsx?$/, loader: 'babel-loader' }, { test: /\.(jpe?g|png|gif)$/, 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]' } } } } ] }, ] }, plugins: [ new vueloaderplugin(), new htmlwebpackplugin({ template: path.resolve(__dirname, '../public/index.html') }), new webpack.namedmodulesplugin(), new webpack.hotmodulereplacementplugin(), ] }
5.4 生产环境配置
const path = require('path') const merge = require('webpack-merge') const webpack = require('webpack') const webpackconfig = require('./webpack.config') const minicssextractplugin = require('mini-css-extract-plugin') const optimizecssnanoplugin = require('@intervolga/optimize-cssnano-plugin'); const cleanwebpackplugin = require('clean-webpack-plugin') const copywebpackplugin = require('copy-webpack-plugin') module.exports = merge(webpackconfig, { mode: 'production', devtool: '#source-map', optimization: { splitchunks: { cachegroups: { vendors: { name: 'chunk-vendors', test: /[\\\/]node_modules[\\\/]/, priority: -10, chunks: 'initial' }, common: { name: 'chunk-common', minchunks: 2, priority: -20, chunks: 'initial', reuseexistingchunk: true } } } }, module: { rules: [ { test: /\.(scss|sass)$/, use: [ { loader: minicssextractplugin.loader }, { loader: 'css-loader', options: { importloaders: 2 } }, { loader: 'sass-loader', options: { implementation: require('dart-sass') } }, { loader: 'postcss-loader' } ] }, ] }, plugins: [ new webpack.defineplugin({ 'process.env': { node_env: 'production' } }), new minicssextractplugin({ filename: 'css/[name].[contenthash:8].css', chunkfilename: 'css/[name].[contenthash:8].css' }), new optimizecssnanoplugin({ sourcemap: true, cssnanooptions: { preset: [ 'default', { mergelonghand: false, cssdeclarationsorter: false } ] } }), new copywebpackplugin([ { from: path.resolve(__dirname, '../public'), to: path.resolve(__dirname, '../dist') } ]), new cleanwebpackplugin() ] })
5.5 修改package.json
"scripts": { "serve": "webpack-dev-server --config ./build/webpack.dev.js", "build": "webpack --config ./build/webpack.prod.js" },
6 打包分析
有的时候,我们需要看一下webpack打包完成后,到底打包了什么东西,
这时候就需要用到这个模块分析工具了 webpack-bundle-analyzer
- 安装依赖
npm install --save-dev webpack-bundle-analyzer
- 修改
webpack-prod.js
配置,在plugins
属性中新增一个插件
在开发环境中,我们是没必要进行模块打包分析的,所以我们将插件配置在了生产环境的配置项中
- 运行打包命令
npm run build
执行成功后会自动打开这个页面
7. 集成 vuerouter
,vuex
- 首先是安装相关依赖
npm install vue-router vuex --save
7.1 集成 vue-router
- 新增视图组件
在src
目录下新增两个视图组件src/views/home.vue
和src/views/about.vue
// src/views/home.vue <template> <div class="home"> <h2>home</h2> </div> </template> <script> export default { name: 'home', data() { return {}; } }; </script> <style lang="scss" scoped> </style>
about.vue
内容跟 home.vue
差不多,将里面的 home
换成 about
就ok了
- 新增路由配置文件
在 src
目录下新增一个 router/index.js
文件
// src/router/index.js import vue from 'vue' import vuerouter from "vue-router"; import home from '../views/home'; import about from '../views/about'; vue.use(vuerouter) export default new vuerouter({ mode: 'hash', routes: [ { path: '/home', component: home }, { path: '/about', component: about }, { path: '*', redirect: '/home' } ] })
- 修改
main.js
文件
// main.js import vue from 'vue' import app from './app.vue' import router from './router' new vue({ router, render: h => h(app) }).$mount('#app')
- 修改
app.vue
组件
// app.vue // 在 template 中添加 // src/app.vue <template> <div class="app"> hello world </div> <div> // router-link 组件 用来导航到哪个路由 <router-link to="/home">go home</router-link> <router-link to="/about">go about</router-link> </div> <div> // 用于展示匹配到的路由视图组件 <router-view></router-view> </div> </template> <script> export default { name: 'app', data() { return {}; } }; </script> <style lang="scss" scoped> .app { color: skyblue; } </style>
运行 npm run serve
命令,如没配置错误,是可以看到点击不同的路由,会切换到不同的路由视图
7.2 配置路由懒加载
在没配置路由懒加载的情况下,我们的路由组件在打包的时候,都会打包到同一个js
文件去,当我们的视图组件越来越多的时候,就会导致这个 js
文件越来越大。然后就会导致请求这个文件的时间变长,最终影响用户体验
- 安装依赖
npm install @babel/plugin-syntax-dynamic-import --save-dev
- 修改
babel.config.js
module.exports = { presets: [ [ "@babel/preset-env", { usebuiltins: "usage" } ] ], plugins: [ // 添加这个 '@babel/plugin-syntax-dynamic-import' ] }
- 修改
router/index.js
路由配置文件
import vue from 'vue' import vuerouter from "vue-router"; vue.use(vuerouter) export default new vuerouter({ mode: 'hash', routes: [ { path: '/home', component: () => import(/* webpackchunkname: "home" */ '../views/home.vue') // component: home }, { path: '/about', component: () => import(/* webpackchunkname: "about" */ '../views/about.vue') // component: about }, { path: '*', redirect: '/home' } ] })
- 运行命令
npm run build
查看是否生成了home...js
文件 和about...js
文件
7.3 集成 vuex
- 在
src
目录下新建一个store/index.js
文件
// store/index.js import vue from 'vue' import vuex from 'vuex' vue.use(vuex) const state = { counter: 0 } const actions = { add: ({commit}) => { return commit('add') } } const mutations = { add: (state) => { state.counter++ } } const getters = { getcounter (state) { return state.counter } } export default new vuex.store({ state, actions, mutations, getters })
- 修改
main.js
文件 导入vuex
// main.js import vue from 'vue' import app from './app.vue' import router from './router' import store from './store' // ++ new vue({ router, store, // ++ render: h => h(app) }).$mount('#app')
- 修改
app.vue
,查看 vuex 配置效果
// app.vue <template> <div class="app"> <div> <router-link to="/home">go home</router-link> <router-link to="/about">go about</router-link> </div> <div> <p>{{getcounter}}</p> <button @click="add">add</button> </div> <div> <router-view></router-view> </div> </div> </template> <script> import { mapactions, mapgetters } from 'vuex' export default { name: 'app', data() { return {}; }, computed: { ...mapgetters(['getcounter']) }, methods: { ...mapactions(['add']) } }; </script> <style lang="scss" scoped> .app { text-align: center; color: skyblue; font-size: 28px; } </style>
- 运行命令
npm run serve
当点击按钮的时候,可以看到我们的getcounter
一直在增加
8 总结
到目前为止,我们已经成功的自己搭建了一个 vue
开发环境,不过还是有一些功能欠缺的,有兴趣的小伙伴可以交流交流。在搭建过程中,还是会踩很多坑的。
如果还不熟悉 webpack 的话,建议自己搭建一次。可以让自己能深入的理解 vue-cli
替我们做了什么
推荐阅读
欢迎关注
欢迎关注公众号“码上开发”,每天分享最新技术资讯
上一篇: JavaScript_day02
下一篇: Java中的对象转型