在学会使用loader之后,我们再来看看webpack的plugin的使用。
导读
本章知识点:plugin/babel/setting
直通车-> webpack插件介绍
如果你还不会使用webpack的基本使用和loader的使用,建议您先观看我的大声对webpack4.0说声你好前两个章节。因为我们例子就是结合上部分的继续讲,只要你跟着我的文章一直 敲代码,你就能很轻松的学会webpack。
使用插件生成html
在之前的例子中,我们都是通过npx webpack xxx打包之后,在dist目录下手动的新建一个xx.html,然后预览我们的效果,这样的操作就比较麻烦,这个时候我们就需要通过插件的方式来打包,让我们的效率增倍。
删除我的dist目录。
然后安装插件
npm install --save-dev html-webpack-plugin
修改一下webpack.config.js的配置
const htmlPlugin = require('html-webpack-plugin'); // 引入html打包插件
plugins: [
new htmlPlugin()
],
引入了我的实例化对象。然后我再来进行一次打包。这个时候就直接给我们生成了dist目录,并且在dist目录下主动为我们生成了index.html,然后打开就可以预览到我们的效果。
作用:htmlWebpackPlugin会在打包结束后,自动生成一个html文件,并把打包后生成的js自动引入到这个html中。
回想之前的例子,我们如果将一个元素挂载到id=root的div下面的话,其实我们这个index.html中是并没有生成root这样的div的,那这个时候我们又应该怎么做呢?
修改插件的配置如下,将我们的自己的index.html作为模板文件,这时就会使用我们自己的模板文件的内容进行打包。
plugins: [
new htmlPlugin({
template: './index.html'
})
],
这个时候是不是插件就跟vue的声明周期一样,在你打包的时候,去完成一些事情。
打包前自动删除dist目录
我们先做一个小测试,就是我们改变一下我们输入的js名称。
output: {
filename: 'dist.js', // 之前是main.js
path: path.resolve(__dirname, 'dist'), // 打包到dist文件夹
}
然后进行打包操作。然后发现我们在dist目录下生成了一个dist.js这个的确是我想要的,但是其他的生成内容好像并不是我希望的,因为我如果以后东西多了,我根本就不知道哪些是我想要的内容,所以我们在打包之前,我希望删除dist目录,这样就能保证里面的内容都是我所需要的。
clean-webpack-plugin
// 安装插件
npm install clean-webpack-plugin -D
// 引入插件
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
plugins: [
new htmlPlugin({
template: './index.html'
}),
new CleanWebpackPlugin()
],
ok,继续打包。这样就只会有本次打包的内容。
插件小总结
插件的内容比较简单,但是我们每次打包的时候应该如何去找到我们的插件呢?webpack的官网是有很多插件的,但是我们详细的去读肯定是有些难度,所以每次使用之前可以百度、谷歌一些打包内容,然后搜索相应的插件进行下载。
Entry与Output的基础配置
Entry入口配置
先回顾一下第一节的知识,知道这两个名词到底是什么?
《大声对webpack4.0说声你好之webpack的基本使用(一)》
entry: 打包的入口文件(npx wbpack 就可以直接打包, 不声明每次打包之前需要告诉webpack你想打包哪个文件)
output:输出文件名称 (打包js之后,默认生成的文件名称,不说明默认是main.js)
so easy.
那我如果要变更一下需求,我将index.js打包两次,分别叫main.js/sub.js,那我们应该怎么做的?
entry: {
main: './src/index.js',
sub: './src/index.js'
},
entry配置项详解:官网entry详解直通车
output
打包不出意外的失败了,因为我们定义了两个文件,但是打包的输入内容只有一个dist.js,所以我们可以修改一下输入的配置。还记得第二节中讲到的占位符吗?
《大声对webpack4.0说声你好之loader基础篇资源打包讲解(二)》
output: {
filename: '[name].js', // name 就是相当于entry文件对应的key,既main sub
path: path.resolve(__dirname, 'dist'), // 打包到dist文件夹
}
然后神奇的发现,我们的index中生成了sub与main两个js文件,这个是因为我们之前使用了htmlwebpackplugin插件,他帮我们自动引入了。
<!doctype html><html><head><meta charset="utf-8"><title>Webpack App</title><meta name="viewport" content="width=device-width,initial-scale=1"></head><body><script src="main.js"></script><script src="sub.js"></script></body></html>
我们继续升华一下我们的这次打包操作,比如我打包之后,我们文件之后是要放在第三方的cdn上面的,这样的话就会在打包文件的前面自动加上一个cdn的域名,这个时候又该怎么办呢?当然手动改是不可能的~!
output: {
publicPath: 'http://cdn.com/',
filename: '[name].js', // 打包后生成的main.js
path: path.resolve(__dirname, 'dist'), // 打包到dist文件夹
}
再次打包(npm rub build -- 我自己修改的scripts,详见系列一)
<script src="http://cdn.com/main.js"></script>
<script src="http://cdn.com/sub.js"></script>
It's perfect.
output配置项详解:官网output详解直通车
SourceMap
在介绍这个配置之前,我们还是先进入一个例子,他会让你很清楚的知道SourceMap能帮你干什么。
新建一个source.js,然后修改一下我们之前修改的配置
// source.js
consele.log('hello, axin !') // console故意写错
// webpack.config.js
// 我们现在的模式是开发者模式,默认是配置了SourceMap的,所以我们需要自己关闭
mode: 'development',
devtool: 'none',
entry: {
main: './src/source.js'
},
output: {
filename: 'dist.js', // 打包后生成的main.js
path: path.resolve(__dirname, 'dist'), // 打包到dist文件夹
}
即使我们写错了,我们的打包依然可以继续完成,但是在页面中说我们的consele未定义,然后点击进去发现是打包之后的文件报错,但是我并不知道我原文件中是哪一行报错,不然我都不知道去哪里修改。
SourceMap:映射关系,它知道dist目录下打包文件的报错信息具体是你src目录下的哪一行
devtool: 'source-map'
这个时候打包,他就会告诉我们src下面的source文件的第一行报错了。而且我们会在dist目录下生成一个dist.js.map的文件,他就是我们文件的对应关系,如果使用inline-source-map,就不会出现map文件,但是会有一个base64的字符串作为映射联系。自觉上车->官方直通车。
推荐形式: 开发环境中,cheap-module-eval-source-map,打包速度快,提示也比较全。
那如果开发的代码放到线上,出现的问题,我们应该怎么配置呢?cheap-module-source-map
webpackDevServer
目前的操作中,我们是修改了我们的代码,然后使用npm run build,最后在dist文件下,找到我们的index.html,在浏览器中打开。那么如此麻烦的操作我们应该如何优化从而提高开发效率呢?
"scripts": {
"build": "webpack",
"watch": "webpack --watch"
},
build是我自己新增的打包代码命令,详解在系列一。
watch可以监听我们的源代码发生变化,他就会自动帮我们重新打包。
所以我们直接使用命令,就可以实现我们的该操作。
npm run watch
监听源文件变化重新打包,然后渲染到我们页面,然后刷新就可以用了,但是这样操作还不够好,因为我希望我在第一次使用这个命令的时候,自动帮我打包,然后打开浏览器,还能模拟一些服务器上的特性。
这个时候我们就可以借助webpackdevserver来达到更加酷炫的效果。
先安装webpack-dev-server
npm install webpack-dev-server -D
修改配置项
// webpack.config.js
devServer: {
contentBase: './dist', // 借助webpack启动服务器,根目录就是打包之后的dist文件夹
open: true // 启动npm run start的时候自动打开浏览器
},
// package.json
"scripts": {
"build": "webpack",
"watch": "webpack --watch",
"start": "webpack-dev-server"
},
然后我们在执行npm run watch,修改源代码,就不需要重新打开浏览器进行手动刷新,webpack-dev-server已经帮我们做了这些操作,所以我们只关注我们的业务代码就ok。
继续扩展,gogogo!
在vue中,我们经常使用proxy来代理我们的跨域服务,因为在webpack中就是支持这种操作的。
// 跨域转发代码
proxy: {
'/api': 'http://localhost:3000'
}
// 配置端口号
port: 8080
官网直通车:www.webpackjs.com/configurati…
接下来我们看一个比较高逼格的操作。
手写自己的server
新建一个scripts命令
"scripts": {
...
"server": "node server.js"
},
因为用到了node,所以我使用expres和一个webpack-dev-middleware
npm install express webpack-dev-middleware -D
修改一个webpack配置确保我的文件打包到根目录下
output: {
publicPath: '/',
filename: 'dist.js', // 打包后生成的main.js
path: path.resolve(__dirname, 'dist'), // 打包到dist文件夹
}
我们就会在根目录下新建一个server.js,运行npm run server的时候就会进入我们自己的脚本
// 引入express webapck 中间件
var express = require('express')
var webpack = require('webpack')
var webpackDevMiddleware = require('webpack-dev-middleware')
// 引入webpack配置文件
var config = require('./webpack.config.js')
var complier = webpack(config) // config配置传入 complier主要做编译
const app = express()
app.use(webpackDevMiddleware(complier, {
publicPath: config.output.publicPath // 声明自己的publicPath
}))
app.listen('3000', () => { // 监听3000端口启动成功
console.log('server is running')
})
在使用 npm run server 就可以启动自己的服务命令了。
代码比较多,我打了一些注释,可能很多人还是看不懂,我再来梳理一下这个流程。
server使我们手写的一个类似于wbpack-dev-server的功能,所以我们在运行npm run server的时候,实际上是运行了一个js文件,利用express我们创建了一个server应用,端口是3000,webpackDevMiddleware是webpack的中间件,complier返回的是一个编译器,只要文件改变了就会重新编译。
所以webpack不止在命令行中运行,还可以在nodejs中运行。
一行代码声明入口文件与出口文件
webpack index.js -o dist.js
这句话就是打包index.js后打包文件是dist.js
Hot Module Replace
devserver的好处
热模块替换,简写HMR
我们先在配置中修改一下配置
// 删除打包的dist目录
// 之前的package.json 删除build,server,
"build": "webpack",
"watch": "webpack --watch",
"start": "webpack-dev-server",
"server": "node server.js"
// 只保留一个start
"scripts": {
"start": "webpack-dev-server"
},
// 启动服务
npm run start
这个时候我们会发现,webpack-dev-server并没有在我们的根目录下生成一个dist目录,但是我们的代码还可以正常运行,这个过程中webpack其实也是对我们的代码进行的代码,不过文件在我们的内存中,所以打包的效率更高。
css热更新
继续,我们在src新建btn.js,statics/style/btn.css
// btn.js
var btn = document.createElement('button')
btn.innerHTML = '新增'
document.body.appendChild(btn)
btn.onclick = function(){
var div = document.createElement('div')
div.innerHTML = 'item'
document.body.appendChild(div)
}
// btn.css
div:nth-child(2n){
background-color: yellow;
}
然后修改入口文件问btn.js,这段代码就是在body里面新增一个btn按钮,点击一次新增一个item的div。偶数的div背景会是一个黄色。
但是我们如果去修改css中背景色,这个时候浏览器就会自动刷新,之前点击新增的div已经完全没有了,这样我们就会重新点击新增来添加节点,这样就会非常麻烦,那么我们在修改css的时候,能保证只是重新加载样式的话,就好多了。
在devServer中添加如下配置。
const webpack = require('webpack') // 引入webpack插件
devServer: {
contentBase: './dist', // 借助webpack启动服务器,根目录就是打包之后的dist文件夹
open: true, // 启动npm run start的时候自动打开浏览器
proxy: { // 配置代理
'/api': 'http://localhost:3000'
},
port: 8080, // 配置端口号
hot: true, // 开启热更新
hotOnly: true // 就算是html文件没生效也不刷新页面
},
plugins: [
new htmlPlugin({
template: './index.html'
}),
new cleanPlugin(['dist']),
new webpack.HotModuleReplacementPlugin() // 引入插件 html才能生效
],
因为改了webpack配置文件内容,所以我们重启一下服务,这样的话,我们修改css文件就不会重新刷新页面,只会重载样式。
ok,再来升华一个例子。在src下新建count.js与number.js
html热更新
我们还是先注释添加的hot,hotonly以及插件
// webpack.config.js
// count.js
function count(){
var div = document.createElement('div')
div.setAttribute('id', 'count')
div.innerHTML = 1
div.onclick = function () {
div.innerHTML = parseInt(div.innerHTML, 10) + 1 // 按照十进制解析+1
}
document.body.appendChild(div)
}
export default count
// number.js
function number() {
var div = document.createElement('div')
div.setAttribute('id', 'number')
div.innerHTML = 1000
document.body.appendChild(div)
}
export default number
解释:count方法新增了一个div。id为count,初始文本为1,有一个点击事件,点击一次内容按照十进制解析,然后+1,最后挂载到body上面,导出方法。number方法就自己悟。
然后在btn.js中引入该方法。
import count from './count'
import number from './number'
count()
number()
我们点击count,内容会发生变化,但是我们去修改number的值,会发现页面刷新了,这个时候就和遇到的css问题一样。
但是我们开启之后,我们就会发现,我们修改了number中的数字之后,页面并没有刷新了,这时我们需要手动在btn.js中加入以下代码。
if(module.hot){
module.hot.akccept('./number', ()=>{ // 监听文件
number()
})
}
这样就会监听我们的number文件中的代码发生了变化,就会重新执行number函数。
新的问题就出现了,因为我的number文件发生了变化,我就重新执行了number函数,然后挂载到了body,但是我并不想这样,我想的是把之前的number修改成我的数即可。
// 在执行方法之前,删除之前的节点
document.body.removeChild(document.getElementById('number'))
BABEL
初探
babel在所有的框架中,我们都能见到他的身影。它可以帮我们更好的处理es6语法。
为什么使用babel
为什么要使用babel处理es6语法?因为我们在大多数浏览器中。例如ie低版本等,代码中写了promise、async await等,他会不识别我们的语法,这个时候就需要将我们的es6语法转到es5。方便浏览器阅读执行代码。
babel官方介绍webpack中使用教程:babeljs.io/setup#insta…
1.安装loader
npm install --save-dev babel-loader @babel/core
babel-loader相信大家已经很熟悉了,它是帮助我们使用webpack做打包的一个工具。babel-core是babel的一个核心库,它能让babel去识别js里面的内容,然后把js转换为抽象语法树,在编译转换成新的语法出来。
2.检测文件如果是js文件,那么使用babel-loader来进行语义上的分析
{
test: /\.js$/,
exclude: /node_modules/, // 如果你的js文件在 node_modules里面,就不使用babel-loader
loader: "babel-loader"
},
3.安装@babel/preset-env文件
npm install @babel/preset-env --save-dev
options: {
"presets": ["@babel/preset-env"]
}
问什么会安装这个文件?当我们的babel-loader处理js文件的时候,babel-loader只是babel和webpack做通讯的,但实际上babel-loader并不会将你把es6语法转成es5语法,你还需要一些其他的模块,才能转换。
这样我们就配置好了,已经可以将es6转义成es5语法。
打包业务文件
新增p.js并修改配置文件
// 打包入口
entry: {
main: './src/p.js',
},
// p.js
const arr = [
new Promise(()=>{}),
new Promise(()=>{})
]
arr.map(item=>{
console.log(item)
})
编译之后我们的const 变成了var 但是promise map等还是有些浏览器是识别不了的。
// 编译后
var arr = [new Promise(function () {}), new Promise(function () {})];
arr.map(function (item) {
console.log(item);
});
这个时候我们还需要借助polyfill
npm install --save @babel/polyfill
安装完成之后我们只需要在代码之前引入就可以了。把它放到业务代码的最顶部。
import "@babel/polyfill";
这个时候在打包,文件就会变得非常大,因为他会自动帮我们生成promise和map等的实现。
但是在我们目前的代码中,只需要promse和map的实现,所以我们还需要增加配置。
options: {
"presets": [["@babel/preset-env",{
useBuiltIns: "usage"
}]]
}
相比于之前的430kb,现在还有81kb,代码就精简了很多。
更多配置项:babeljs.io/docs/en/bab…
"targets": {
"chrome": "67" // 大于67的版本就不注入
}
...
打包库文件
如果我们平时自己做一些东西,完全可以使用polyfill,但是如果做一些比较大的ui库等,这种就不适合了,因为他相当于一个全局使用,会污染到我们的代码。
npm install --save-dev @babel/plugin-transform-runtime
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"absoluteRuntime": false,
"corejs": false,
"helpers": true,
"regenerator": true,
"useESModules": false,
"version": "7.0.0-beta.0"
}
]
]
如果将corejs改成2,需要额外的安装@babel/runtime-corejs2
他会帮你以组件的方式引入,不会污染你的全局变量。
提出babel配置
这个时候我们的代码就已经非常多了,所以我们可以将options的代码提出来,新建.babelrc文件放入其中
// .babelrc
{
"presets": [
[
"@babel/preset-env",
{
"useBuiltIns": "usage",
"targets": {
"chrome": "67" // 大于67的版本就不注入
}
}
]
]
}
总结
- 使用插件,htmlWebpackPlugin/clean-webpack-plugin
- webpack.config.js配置讲解
- sourcemap文件映射等
- webpack-dev-serser热更新等
- 强大的babel
写了这么久,收到了不少大神的建议,写文章的水平也比较低,第四节开始慢慢开始改变吧,加油加油奥利给~!