从零开始 Webpack 4 配置 Vue项目
2019-06-27
超详细!看不懂就来搬代码好了! 怎么可能看不懂~
我们不生产代码,我们只是代码的搬运工。 ——鲁迅
创建项目
打开Terminal并跳转到你希望的目录下,然后创建一个新的文件夹。
> mkdir vue-init
进入该目录。
> cd vue-init
npm 初始化。-y | --yes
默认所有配置项都为yes。
> npm init -y
安装webpack相关包
webpack:前端打包工具
webpack-cli:在webpack 3中,webpack和CLI都在同一个包中,但在第4版中,两者被分开了,因此这里必须安装CLI
webpack-dev-server:用于起一个本地服务器
webpack-merge:用于合并webpack基础配置项
-D | --save-dev
将包记录到"devDependencies"
中,-S | --save
将包记录到"dependencies"
中,
两者的区别即是否会在生产环境中被使用。若在生产环境中需要用到,则记录到dependencies中,反之则记录到devDependencies中。
> npm i webpack webpack-cli webpack-dev-server webpack-merge -D
安装其他需要用到的包
Vue
Vue文件解析
> npm i vue-loader vue-template-compiler -D
Vue、VueRouter及Axios
> npm i vue vue-router axios -S
Javascript
Js语法解析(如ES6、ES7等浏览器暂不支持的语法)
> npm i @babel/core @babel/preset-env babel-loader -D
Js文件压缩
> npm i uglifyjs-webpack-plugin -D
Style
Css Loader
> npm i css-loader style-loader -D
Sass Loader
> npm i node-sass sass-loader -D
Postcss Loder & Autoprefixer
> npm i postcss-loader autoprefixer -D
分离、压缩Css文件
> npm i mini-css-extract-plugin optimize-css-assets-webpack-plugin -D
图片
图片loader
> npm i file-loader url-loader -D
图片压缩
> npm i image-webpack-loader -D
其他
HTML模板解析
> npm i html-webpack-plugin -D
清除dist文件夹
> npm i clean-webpack-plugin -D
项目目录
为之后行文方便,在此把项目全部目录列出来。
可以先把同名文件和文件夹创建好,占坑~
vue-init
|-- build
|-- webpack.base.js
|-- webpack.dev.js
|-- webpack.prod.js
|-- dist
|-- public
|-- json
|-- pictureList.json
|-- index.html
|-- mocker.js
|-- src
|-- entry
|-- index.js
|-- imgs
|-- pages
|-- components
|-- nav.vue
|-- App.vue
|-- Index.vue
|-- List.vue
|-- style
|-- app.scss
|-- common.scss
|-- index.scss
|-- nav.scss
|-- .babelrc
|-- .gitignore
|-- package-lock.json
|-- package.json
|-- postcss.config.js
编写webapck配置
url-loader对未设置或者小于limit设置的图片进行转换,以base64的格式被img的src所使用;而对于大于limit byte的图片用file-loader进行解析。
图片打包成base64可以减少网络请求,但代价是样式文件将变大。
因此需要根据实际情况进行取舍。
// webpack.base.js
// 存放通用配置
const webpack = require('webpack'),
path = require('path'),
// 用于解析vue文件
VueLoaderPlugin = require('vue-loader/lib/plugin'),
// html文件插件,将静态文件引入html中
HTMLWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './src/entry/index.js', // 项目的入口文件
module: {
rules:[
{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/
},
{
test: /\.vue$/,
loader: 'vue-loader'
},
{
test: /\.(sa|sc|c)ss$/,
use: [
'style-loader',
'css-loader',
'postcss-loader',
'sass-loader'
]
},
{
test: /\.(png|jpg|svg|gif)$/,
use: [
{
loader: 'url-loader',
options: {
limit: 5000,
name: 'imgs/[name].[ext]'
}
}
]
}
]
},
plugins: [
new VueLoaderPlugin(),
// 自动将静态资源引入html文件中
new HTMLWebpackPlugin({
template: path.resolve(__dirname, '../public/index.html')
})
]
};
devServer中是开发时所需的重要配置。
// webpack.dev.js
// development环境
const merge = require('webpack-merge'),
common = require('./webpack.base'),
path = require('path'),
apiMocker = require('webpack-api-mocker');
module.exports = merge(common, {
mode: 'development',
devtool: 'inline-source-map',
devServer: {
// 入口页面所在文件夹
contentBase: '../public',
// 将所有请求都打回index.html,因为此处配置是单页面应用
historyApiFallback: true,
// 本地mock数据配置,mock数据配置在mocker.js中
before: function(app) {
apiMocker(app, path.resolve('public/mocker.js'));
},
// 监听端口
port:8889,
// 启动后自动打开网址
open: 'http://localhost:8889/',
// 让同网域其他设备访问本地服务
host:'0.0.0.0'
},
output: {
filename: 'js/[name].[hash:5].js',
path: path.resolve(__dirname, '../dist')
}
});
webpack.prod.js文件中新增了很多包,使得js和css文件、图片更加规整简洁。
// webpack.prod.js
// production
const merge = require('webpack-merge'),
common = require('./webpack.base'),
path = require('path'),
// 清除dist文件夹
{CleanWebpackPlugin} = require('clean-webpack-plugin'),
// 处理、打包css文件 功能类似style-loader
MiniCssExtractPlugin = require('mini-css-extract-plugin'),
// 用于压缩css文件
OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin"),
// 压缩js文件
UglifyJsPlugin = require("uglifyjs-webpack-plugin");
module.exports = merge(common, {
mode: 'production',
output: {
filename: 'js/[name].[hash:5].js',
path: path.resolve(__dirname, '../dist')
},
optimization: {
// 将引入的第三方库文件单独打包
splitChunks: {
// 所有引入的文件都打包
chunks: 'all',
cacheGroups: {
vendor: {
name: 'vendor',
test: /[\\/]node_module[\\/]/,
priority: -10,
chunks: 'initial'
}
}
}
},
module: {
rules: [
{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/
},
{
test: /\.vue$/,
loader: 'vue-loader'
},
{
test: /\.(sa|sc|c)ss$/,
use: [
{
// 使用MiniCssExtractPlugin.loader替代style-loader
loader: MiniCssExtractPlugin.loader,
options: {
// 解决图片路径不对的问题
publicPath: '../'
}
},
'css-loader',
'postcss-loader',
'sass-loader'
]
},
{
test: /\.(png|jpg|svg|gif)$/,
use: [
{
loader: 'url-loader',
options: {
limit: 10240,
name: 'imgs/[name].[contenthash:5].[ext]'
}
},
{
// 对图片进行压缩处理,配置项参考官方文档
loader: 'image-webpack-loader',
options: {
mozjpeg: {
progressive: true,
quality: 65
},
optipng: {
enabled: false
},
pngquant: {
quality: '65-90',
speed: 4
},
gifsicle: {
interlaced: false
},
webp: {
quality: 75
}
}
}
]
}
]
},
plugins: [
new CleanWebpackPlugin(),
new MiniCssExtractPlugin({
filename: 'css/[name].[hash:5].css'
}),
new OptimizeCSSAssetsPlugin(),
new UglifyJsPlugin({
uglifyOptions: {
compress: {
// 去除debugger和console
drop_debugger: true,
drop_console: true
}
},
cache: true,
parallel: true,
sourceMap: false
})
]
});
编写其他配置
"presets"
参数由插件名和参数对象组成一个数组进行传递。
// .babelrc
{
"presets": [
[
"@babel/preset-env",
{
"targets": {
// 此处为浏览器的查询条件
// 1%:基于全球使用率统计而选择的浏览器版本范围
// last 2 version:每个浏览器的最近两个版本
// not ie <= 8:不小于等于ie8的浏览器版本
"browsers": ["> 1%", "last 2 version", "not ie <= 8"]
}
}
]
]
}
postcss增加不同浏览器css样式相关前缀。
// postcss.config.js
module.exports = {
plugins: [
require('autoprefixer')
]
};
在package.json中,加入运行命令。--hot
热加载。当代码有变化时,会自动刷新页面。--open
启动项目时,自动在浏览器打开网页。--env.NODE_ENV=development
设置环境变量,可以在页面中取到当前所处的环境,进行不同的设置。--config <filepath>
需要读取的配置文件路径。
// package.json
// ...
"scripts": {
"start": "webpack-dev-server --hot --open --env.NODE_ENV=development --config build/webpack.dev.js",
"build": "webpack --env.NODE_ENV=production --config build/webpack.prod.js"
},
// ...
编写页面代码
// index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8"/>
<title>Vue init</title>
</head>
<body>
<!-- 留出坑来给Vue发挥 -->
<div id="app"></div>
</body>
</html>
index.js是连接webpack和Vue项目的重要文件,在此对Vue项目进行一些基础配置并作为webpack打包的入口。
// index.js
import Vue from 'vue';
import VueRouter from 'vue-router';
import axios from 'axios';
import '../style/common.scss';
import App from '../pages/App.vue';
import Index from '../pages/Index.vue';
import List from '../pages/List.vue';
// 将VueRouter注册到Vue上
Vue.use(VueRouter);
// 将axios挂载到Vue原型链上,方便使用
Vue.prototype.$ajax = axios;
// 创建路由列表
const router = new VueRouter({
routes: [{
path: '/',
component: Index
},
{
path: '/list',
component: List
}
]
});
// 创建Vue实例
new Vue({
el: '#app',
router,
render: h => h(App)
});
App.vue作为整个项目兜底的页面,可以将公共的部分都提取到App.vue页,此处还有一个VueRouter的配置需要注意。
// App.vue
<template>
<div id="app">
<myNav></myNav>
<!-- ⚠️给VueRoutter留坑!! -->
<router-view></router-view>
</div>
</template>
<script>
// 引入导航栏组件
import myNav from "./components/nav.vue";
export default {
// 注册组件
components: {
myNav
}
};
</script>
来看index.vue页面。
// index.vue
<template>
<div id="index">
<h1>hello world!</h1>
<p>halo Viola</p>
<div v-for="(img, index) in imgList" :key="index">
<img :src="img">
</div>
<div class="nice-pic"></div>
</div>
</template>
<script>
export default {
name: "index",
data: function() {
return {
imgList: []
};
},
mounted() {
this.$ajax.get("/api/pictureList").then(res => {
let result = res.data;
if(result.code !== 200) return;
this.imgList = result.data;
});
}
};
</script>
在index.vue页面中用到了异步请求。异步请求是前端开发中最基本的需求之一。如果前端开发还需要等待接口先开发出来,那浪费的时间估计都能完成开发了;所以在本地mock数据是前端必备技能。
// mocker.js
const fs = require('fs');
function fromJSONFile(filename) {
return (req, res) => {
// 读取mock数据json文件
const data = fs.readFileSync(`public/json/${filename}.json`).toString();
// 解析json文件
const json = JSON.parse(data);
return res.json(json);
};
}
const proxy = {
// 格式:'请求头 请求路径'
'GET /api/pictureList': fromJSONFile('pictureList')
};
module.exports = proxy;
页面样式通过单独的scss文件编写(为什么要单独编写样式?见下文 遇到的问题 )
// common.scss
// 引入各个页面的样式
@import './app.scss';
@import './index.scss';
@import './nav.scss';
此处仅列举index.scss作为示例。
// index.scss
// sass语法
$width: 50rem;
#index {
margin-top: 60px;
// postcss将自动添加前缀
transform: rotate(0deg);
img {
width: $width;
}
.nice-pic {
width: 80%;
height: $width;
margin: 0 auto;
// 背景图引入,小于10kb将被编译成base64
background: url('../imgs/1.jpg');
}
}
遇到的问题
提取Vue文件中的Css
因为Vue文件中的样式代码会默认打包进js中,因而生成的js文件非常庞大,可能会导致首屏加载速度非常慢,直接影响用户体验,所以执念的想要把样式文件分离出来。在网上搜索了好半天,找到了官方文档以及民间方案,但是一一尝试之后都没有达到效果。考虑到这可能是webpack 4及相关包实现的问题,因而不想再在这个问题上纠结,便选择了Plan B。
Plan B即将样式文件放到style文件夹中,就各个页面单独一个样式文件进行维护,再引入enrty/index.js中进行编译,此方案可以达成css和js分离的目的。
在下方还是放出我在网上搜索到的官方+民间方案,诸君若感兴趣可以自行尝试。
官方网页:CSS 提取
民间网页: webpack4实现css打包
参考网页:
wow!~完结撒花 🎉🎉🎉
趁着设计还没给定稿,摸鱼写了代码和博客,接下来就是业务逻辑开发啦~
敬请期待~ 😉