一个现代化的web应用,已经不是单纯地优html、css、javascript组成的,它还需要对应用进行打包、压缩和编译成浏览器能够理解的代码,于是webpack就开始流行起来了。
webpack是一个模块打包器,它可以打包任何东西。你可以在开发时使用最新的Javascript特性或Typescirpt,webpack会将它编译成浏览器支持的代码并压缩它;你还可以在Javascript中导入需要用到的静态资源。
这篇文章算是一个较为完整的实战教程,目标是搭建一个可用的脚手架,在此基础上可以扩展出更多的功能。
架构需要支持的特性:
新建一个项目,进入项目根目录,创建默认的package.json
yarn init -y
安装webpack和webpack-cli
yarn add webpack webpack-cli -D
在根目录下新建一个webpack.config.js
Entry
入口文件,webpack会首先从这里开始编译
// webpack.config.js
const path = require('path');
module.exports = {
entry: {
app: './src/index.js'
},
}
Output
定义了打包后输出的位置,以及对应的文件名。[name]是一个占位符,这里是根据我们在entry中定义的key值,即等价于app
module.exports = {
/* ... */
output: {
path: path.resolve(__dirname, './dist'),
filename: '[name].bundle.js',
},
}
确保src下有index.js,然后现在可以使用我们的最小化配置进行打包。在package.json中加入以下代码
"scripts": {
"build": "webpack"
}
运行该命令
yarn run build
可以在命令行中看到打包的结果,并且在根目录下生成了一个dist目录,说明打包成功
Plugins
插件使webpack具备可扩展性,可以让我们支持更多的功能
模板文件:Html-webpack-plugin
当我们构建一个web app的时候,我们需要一个HTML页,然后再HTML中引入Javascript,当我们配置了打包输出的bundle文件是随机字符串时,每次手动更新就特别麻烦,所以最好的方法是可以自动将bundle打包进HTML中。
Html-webpack-plugin - 从模板生成一个HTML文件
安装
yarn add html-webpack-plugin -D
在根目录下新建一个文件public/index.html,内容如下
<!DOCTYPE html>
<html lang="en">
<head>
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<div id="root"></div>
</body>
</html>
其中title是读取html-webpack-plugin插件的配置,配置如下
// webpack.config.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
/* ... */
plugins: [
new HtmlWebpackPlugin({
title: '我是title',
template: path.resolve(__dirname, './public/index.html'),
filename: 'index.html',
}),
],
}
现在我们再次运行yarn run build,可以看到dist下多了一个index.html,其中自动插入了标题和script
打包前清除dist:clean-webpack-plugin - 打包前移除/清理 打包目录
安装
yarn add clean-webpack-plugin -D
配置
const path = require('path')
const {CleanWebpackPlugin} = require('clean-webpack-plugin')
module.exports = {
/* ... */
plugins: [
/* ... */
new CleanWebpackPlugin(),
],
}
命令行友好提示:friendly-errors-webpack-plugin
安装
yarn add friendly-errors-webpack-plugin -D
配置
// webpack.config.js
const friendlyErrorsWebpackPlugin = require('friendly-errors-webpack-plugin');
module.exports = {
plugins: [
new friendlyErrorsWebpackPlugin(),
]
}
Loaders
webpack使用loaders去解析模块,webpack想要去如何理解Javascript、静态资源(图片、字体、css)、转移Typescript和Babel,都需要配置相应的loader规则。
在项目中只有一个HTML和一些Javascript是没什么用的,我们还需要webpack能够做一些事:
Babel
Babel 是一个 JavaScript 编译器,能将 ES6 代码转为 ES5 代码,让你使用最新的语言特性而不用担心兼容性问题,并且可以通过插件机制根据需求灵活的扩展,我们需要先安装以下库
yarn add babel-loader @babel/core -D
配置
// webpack.config.js
module.exports = {
/* ... */
module: {
rules: [
// JavaScript
{
test: /\.m?js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
},
},
},
],
},
}
在 Babel 执行编译的过程中,会从项目根目录下的配置文件读取配置。在根目录下创建Babel的配置文件babel.config.json
{
"presets": ["@babel/preset-env"]
}
如果未安装@babel/preset-env需要先安装
yarn add @babel/preset-env -D
图片和字体
解析图片的loader配置
module.exports = {
/* ... */
module: {
rules: [
// Images
{
test: /\.(?:ico|gif|png|jpg|jpeg)$/i,
type: 'asset/resource',
},
],
},
}
解析字体文件的loader配置
module.exports = {
/* ... */
module: {
rules: [
// Fonts and SVGs
{
test: /\.(woff(2)?|eot|ttf|otf|svg|)$/,
type: 'asset/inline',
},
],
},
}
样式
现在我们希望能够在Javascript中导入CSS,以及将CSS注入DOM,另外还想使用CSS的高级特性,如cssnext,需要依赖一下库
css-loader - 解析CSS导入
style-loader - 将CSS注入DOM
postcss-loader - 用PostCSS处理CSS
postcss-loader - 用PostCSS处理CSS
安装
yarn add css-loader style-loader postcss-loader postcss-preset-env postcss postcss-cssnext sass sass-loader node-sass -D
新建PostCSS配置文件postcss.config.js,配置如下
module.exports = {
plugins: {
'postcss-preset-env': {
browsers: 'last 2 versions',
},
},
}
配置loader
// webpack.config.js
module.exports = {
/* ... */
module: {
rules: [
// CSS, PostCSS, and Sass
{
test: /\.(scss|css)$/,
use: ['style-loader', {
loader: 'css-loader',
options: {
importLoaders: 1,
},
}, 'postcss-loader', 'sass-loader'],
},
],
},
}
让我们从设置配置为开发模式开始,表示当前的配置的配置为开发环境的配置
// webpack.config.js
module.exports = {
mode: 'development',
// ...
}
使用source maps
为了在报错的时候更好的追踪代码和给出错误代码出现的地方的提示,我们可以使用source map,配置如下
// webpack.config.js
module.exports = {
devtool: 'inline-source-map'
// ...
}
HMR
当我们改动代码时,希望能自动重新编译代码,webpack提供了三种不同的方式:
大多数情况,使用的是webpack-dev-server,本文也是使用这个,不过我会顺带介绍一下其它两种方式,大家各取所需。
使用监听模式:
// package.json
{
"watch": "webpack --watch"
}
执行以下命令
yarn run watch
现在当我们保存代码的时候会自动编译代码,刷新浏览器后即可看到效果;但是我们想要自动刷新浏览器怎么办,这时候就轮到webpack-dev-server上场了。
webpack-dev-server
它为我们提供了一个服务器和live relaoding的能力,我们需要首先安装它
yarn add webpack-dev-server -D
然后配置如下
// webpack.config.js
module.exports = {
// ...
devServer: {
historyApiFallback: true,
contentBase: path.join(__dirname, './dist'),
open: false,
hot: true,
quiet: true,
port: 8082,
},
}
// package.json
{
"scripts": {
"start": "webpack serve"
}
}
我们在8082端口监听了一个服务,监听的目录是dist,并且支持HMR,现在打开http://localhost:8082,可以看到我们的页面,然后改动代码,浏览器会自动刷新更新效果,是不是很酷!
上面提到了HMR,它的全称是Hot Module Replacement,翻译过来就是热模块替换,我认为它是webpack提供的最有用的一个特性,它允许我们只更新改动过的模块,而不需有全部更新,我们在上面已经开启了该功能,即hot: true。
webpack-dev-middleware
这是一个webpack的中间件,可以让webpack把文件交给一个服务器处理,比如接下来要使用的express,这给了我们更多的控制权,接下来简单演示一下。
安装express和webpack-dev-middleware
yarn add express webpack-dev-middleware -D
更改配置
module.exports = {
//...
output: {
//...
publicPath: '/'
}
}
publicPath可以定义了express监听服务的路径,接下来就创建我们的express`服务器
新建一个server.js
const express = require('express');
const webpack = require('webpack');
const webpackDevMiddleware = require('webpack-dev-middleware');
const app = express();
const config = require('./webpack.config.js');
const compiler = webpack(config);
// Tell express to use the webpack-dev-middleware and use the webpack.config.js
// configuration file as a base.
app.use(
webpackDevMiddleware(compiler, {
publicPath: config.output.publicPath,
})
);
// Serve the files on port 3000.
app.listen(3000, function () {
console.log('Example app listening on port 3000!\n');
});
监听端口为3000,执行以下命令启动服务
node server.js
方便起见,可以将该命令加入package.json
{
//...
"scripts": {
"server": "node server.js"
}
vue2.x:
yarn add vue vue-loader vue-template-loader vue-router vuex
vue3.x:
yarn add vue vue-loader@next @vue/compiler-sfc vue-router vuex
配置
// webpack.config.js
const {VueLoaderPlugin} = require('vue-loader')
module.exports = {
/* ... */
module: {
rules: [
// vue
{
test: /\.vue$/,
use: ['vue-loader'],
},
],
},
plugins:[
new VueLoaderPlugin()
]
}
/src/store/index.js
import Vue from 'vue';
import Vuex from 'vuex';'
Vue.use(Vuex);
export default new Vuex.Store({
state:{},
mutations:{},
actions:{},
modules:{}
})
/src/router/index.js
import Vue from 'vue';
import router from 'vue-router ';
Vue.use(router)
const routes = [
{
path:'/',
name:'home',
compontent:()=> import('./views/home.vue')
},
{
path:'/about',
name:'about',
compontent:()=> import('./views/about.vue')
}
]
const route = new router({
routes
})
export default route
/src/main.js
import Vue from 'vue';
import App from './App.vue';
import store from './store/index.js';
import router from './store/router.js';
new Vue({
store,
router,
render:h => h(App)
}).$amount('#app')
到目前为止,我们搭建了一个简易的vue脚手架,并且它支持cssnext、HMR等特性,对于一个小项目来说已经足够用了,大家可以在此基础上进行扩展。