乐趣区

关于前端:Webpack的核心概念devServerHMR与Babel

[toc]

1. devServer

这里不晓得你有没有发现后面几节课调试的时候一个比拟繁琐的步骤,就是每一次测试打包就须要从新执行 npm run bundle 命令,而后手动关上 dist 目录上面的 index.html 文件看成果,咱们能不能在 src 目录下源代码发生变化的时候主动实现上述过程?

1.1 应用 –watch 参数

第一种办法咱们能够给 webpack 命令加上 –watch 参数:

// package.json
"scipts":{"watch":"webpack --watch"}

他就会监听打包源代码的变动,一旦源代码有变动他就会从新打包。从而更新 dist 目录上面的文件。

这是第一种解决办法。不会帮咱们起服务器,没方法做 ajax 调试,必须手动刷新浏览器。

1.2 webpackDevServer

上述办法只能实现主动打包,然而你想要主动关上文件,并且模仿一些服务器的个性的话是不够的,这里就须要借助 devServer 这个工具,这是一个 webpack 起的本地服务器,能够帮忙咱们监听源代码变动同时主动刷新浏览器。

根底应用

首先须要装置一下:

npm install webpack-dev-server -D

接着配置一下:

// webpack.config.js
devServer:{contentBase:'./dist' // 服务器根门路}

而后在 package.json 文件加一个命令:

"start":"webpack-dev-server"

接着执行 start 命令,log 提醒咱们在本地 8080 端口启动了一个服务,能够拜访,并且此时咱们扭转了源代码,他还会帮忙咱们主动刷新浏览器,所以能够很好的进步开发效率。

当初咱们发现没有 dist 目录了,其实 devServer 自身还是会对 src 进行打包,然而他不会放到 dist 目录上面而是打包放到了内存外面,所以打包速度会变快,不必放心。

同时这种以服务器模式相比之前间接关上本地 index.html 的还有一个益处就是能够发 ajax 申请,因为申请要求必须是在一台服务器上以 http 协定进行,而本地关上是 file 协定,必定不行!而开了服务器就是从本地 8080 端口收回,个别不会有问题 —- 大多数前端框架就是这样应用的。

其余配置

  • open:这个配置容许你执行命令当前主动关上浏览器,主动展现界面。
  • proxy:个别前端框架有时候会配置这个参数来进行接口代理,进行跨域申请
devServer:{
    ...
    proxy:{'/api':'http://localhost:3000'}
}

上述填写之后如果用户拜访 8080 的 api 端口会转发到本地 3000 端口。

  • port:配置 devServer 启动的端口

1.3 本人实现一个相似的服务

这里咱们基于 node 来本人编写一个简略的服务,来模仿 devserver 的上述性能。

首先咱们新增一个命令与对应的 JS 文件:

"server":"node server.js"

上面须要实现这个 server.js 来帮忙咱们创立这样一个性能靠近 devServer 的服务器

搭建环境

首先须要装置 express 或者 koa 来帮忙你疾速搭建服务器,同时你还须要监听 webpack 打包的变动,所以你还须要一个中间件:

npm install express webpack-dev-middleware -D

而后咱们给 webpack 配置文件的 output 字段加一个 publicPath 参数,使得所有打包援用的字段都会加一个 / 根门路:

output:{
    publicPath:'/',
    ...
}

编写 server.js

const express = require('express')
const webpack = require('webpack')

const app = express() // 创立服务器实例
// 监听端口
app.listen(3000,() => {console.log('server is running)
})

接着咱们编写一下 webpack 相干逻辑,这里须要借助 webpack 和 webpackDevMiddleware 的一些 API:

const express = require('express')
const webpack = require('webpack')
const webpackDevMiddleware = require('webpack-dev-middleware')
const config = require('./webpack.config.js'); // 引入配置文件
const complier = webpack(config); // 应用 webpack 配合 config 来随时进行代码编译。返回一个编译

const app = express() // 创立服务器实例
// 应用 complier
// 中间件帮忙监听打包源代码是否有变动
// 只有文件产生扭转了,complier 就会从新运行,对应打包输入内容就是 publicPath
app.use(webpackDevMiddleware(complier),{publicPath:config.output.publicPath})

// 监听端口
app.listen(3000,() => {console.log('server is running)
})

当初你让 complier 编译器从新执行一次,他就会从新打包一次代码~

而后咱们改一下源代码,能够看到控制台会输入打包信息。

然而这里就是浏览器必须手动刷新,而且想要跟 devServer 齐全一样须要配置很多货色。这里就不持续扩大了,感兴趣的能够本人网上搜寻学习材料。

同时这里你也理解了其实 node 中也能够应用 webpack 的,能够实现很多自定义的 webpack 扩大服务~

2. Hot Module Replacement – 热模块更新

这里咱们来看一个问题,当你针对源代码的款式进行批改时,devServer 会帮忙咱们刷新浏览器,然而此时你对 HTML 进行的更新都没有了(例如给页面 append 一个元素),必须从新进行一遍操作。

这里就能够通过 HMR,批改 CSS 后间接更新了页面的 style 就行了,不须要刷新。

2.1 HMR 应用

CSS

devServer:{
    ...
    hot:true,  // 关上 hmr
    hotOnly:true // 即使 hmr 没失效,我也不让浏览器主动刷新,不会做其余解决
}

这样 devServer 配置实现了,此外还须要引入一个 webpack 的插件:

// webpack.config.js
const webpack = require('webpack')
...

plugins:[
    ...
    new webpack.hotModuleRepalcemnetPlugin()]

通过上述配置,HMR 性能就开启了。咱们须要重启一下 devServer 让配置文件失效。

咱们发现 CSS 批改了,并不会影响界面,只会替换 CSS 内容,不会改 JS 渲染出的内容!,大大不便了款式的调试。

JS

对于 JS 文件同样也会有上述 CSS 的问题:每一次批改某个 JS 文件都会发送一个 http 申请,申请 8080,从新刷新,其余 JS 的状态都没有被保留下来。

咱们心愿独立模块的数据扭转不要影响其余模块的内容,这时候能够借助 HMR 来实现。

这里咱们把之前 HMR 相干配置关上,此外还须要一些额定的代码,能力失效:

//index.js

...

counter()
number()

if(module.hot){module.hot.accpet('./number',() => {document.body.removeChild(document.getElementById('number'))
        number();})  // 如果 number 文件产生了变动,就会执行前面函数 --number 从新执行
}

2.2 注意事项

这个 JS 的 HMR 不像 CSS 一样能够间接更新,CSS 其实也要写相似的逻辑,然而在 css-loader 外面曾经帮你解决好了。

在 vue 等框架外面,其实咱们没有写过相似下面的 JS HMR 代码,这是因为 vue-loader 外面也内置了这些解决。
React 外部则是借助了 Babel process 内置了 HMR 的实现。

然而要留神,如果你心愿热更新某些比拟偏门的文件,例如数据文件,默认的解决逻辑可能没有蕴含,就须要手动写相似下面的代码,没方法借助 loader 或者 Bable Process 了。

感兴趣的也能够网上持续学习 HMR 的底层原理

3. Babel

这里咱们解说一下如何联合 Babel 和 Webpack 来解决咱们的 ES6 语法

3.1 问题引入

首先咱们新建一个 index.js 文件,其中应用一些 ES6 语法:

const arr = [new Promise(() => {}),
    new Promise(() => {})
];

arr.map(item=>{console.log(item)
})

上述 index 文件是包含 ES6 语法的,咱们尝试应用 webpack 打包:

npx webpack

这里没有用 devServer 打包,因为这个打包后的文件不会生成到目录外面,而是保留到内存中,所以你看不到的。用 webpack 间接打包就行

咱们来看一下打包生成的内容,简直是一成不变把 index 外面内容引进来。

而之前的代码能不能运行呢,咱们关上 chrome 看一下(用 devServer 启动)。

后果咱们发现如同失常,没有问题,这是因为 chrome 浏览器的较新版本本身实现了对 ES6 的语法解析性能,然而如果咱们应用低版本或者老的 IE 浏览器就有可能报错,最终的起因是因为程序的兼容性较差。

咱们心愿 webpack 打包之后能把 ES6 转换成 ES5 的语法,这样所有浏览器都能够运行了。

3.2 Babel 的介绍和应用

Babel 是一种罕用的 JS 编译器,Babel 能够把 ES6 语法转换为 ES5 语法。

Babel 同 webpack 的配合应用,能够参考官网,有同 webpack 的配合教程。

这里咱们须要装置一个 babel-loader 以及 babel 的外围库 @babel/core,有这个库才能够实现 JS 代码 -> AST -> ES5 代码的过程

npm install --save-dev babel-loader @babel/core

接着依照上述阐明在 config 的 rules 外面加一条规定:

rules:[{
    test:/\.js$/,
    exclude:/node_modules/,
    loader:"babel-loader"
}]

exclude 代表如果你的 js 文件在 node-moudles 就不应用 babel-loader,因为这是第三方的代码,根本都做过这个本义解决了。

接着你还须要装置 @babel/preset 来开启 ES5 的转换性能:

npm install @babel/preset-env --save-dev

这是因为 babel-loader 这是 webpack 和 babel 的桥梁,并不会把 es6 翻译为 es5,还须要借助其余模块,这也就是 preset-env 的性能。

rules:[{
    test:/\.js$/,
    exclude:/node_modules/,
    loader:"babel-loader",
    options:{presets:["@babel/preset-env"]
    }
}]

而后咱们再打个包,看一下 main.js 打包后文件发现箭头函数变成了一般函数,let 变成 var.

此外你还能够配置 target,来阐明哪些浏览器须要进行本义,只有配置的浏览器版本低于你指定的版本号才会应用 babel 进行转换解决,个别这个都会配的,因为浏览器版本高的其实针对 ES6 的兼容都是有的。

rules:[{
    test:/\.js$/,
    exclude:/node_modules/,
    loader:"babel-loader",
    options:{
        presets:[["@babel/preset-env",{
            targets:{
                edge:"17",
                chrome:"67",
                firefox:"60",
                safari:"11.1"
            }
        }]]
    }
}]

3.3 babel/polyfill 应用

然而仅仅做这种转换够吗?还不够,例如数组的 map 办法其实在低版本的浏览器还是不存在的,所以这里不仅仅须要 preset-env 做语法的转换,还须要想方法把缺失的变量或函数补充到代码中。
这里就须要借助 babel-polyfill:

npm install --save @babel/polyfill

留神这里不要加 -dev,因为这个是在代码运行时也须要的。

咱们只须要在须要 polyfill 的中央 import 就能够了,这里咱们把这个 import 放到业务代码 index.js 的顶部

// index.js

import "@babel/polyfill";

const arr = [];

arr.map(...)

这里其实这种写法不太好,这其实把所有的 polyfill 函数都 1 打包进去了,会发现 build 之后的包特地大,这里咱们不心愿 babel 把所有兼容语法都写进去,例如只心愿 polyfill 一下 map 办法,不然包太大了

这里须要一个配置写进去 webpack.config.js,在外面对 presets 字段进行更改,增加一个配置:

rules:[{
    test:/\.js$/,
    exclude:/node_modules/,
    loader:"babel-loader",
    options:{presets:[["@babel/preset-env",{useBuiltIns:'usage'}]]
    }
}]

意思是当我做 polyfill 不是一股弄都加进去,而是看用到什么才加进去,而后再打个包:

留神点:如果你开发的是一个类库或者第三方模块,就不倡议采纳这种形式,因为 polyfill 是采纳全局替换的办法实现兼容的,类库等应用这种形式会净化到全局环境。

3.4 babel/plugin-transform-runtime

打包类库须要换一种配置形式:

这里咱们须要装置两个模块,依照官网阐明。
接着你须要在 webpack 配置参数外面加一个 plugin:

rules:[{
    test:/\.js$/,
    exclude:/node_modules/,
    loader:"babel-loader",
    options:{
        "plugins":[["@babel/plugin-transform-runtime",{
            "corejs":2,
            "helpers":true,
            "regenerator":true,
            "useEsModules":false
        }]]
    }
}]

而后从新打包,这时候会提醒报错,短少一个 corejs2 的模块,这是因为咱们的 corejs 写的是 2,这时候参考文档再装置一个包:

npm install --save @babel/runtime-corejs2

而后就能够了。
如果你写的是业务代码,参考 3.3 应用 polyfill 就能够了。

如果你写的是类库,参考上述去做,防止净化全局环境。

3.5 解决 options 配置项过多问题

线上业务很可能 options 配置会很多,这时候咱们能够根目录下独自写一个.babelrc 文件,把配置项复制进去:

// .babelrc
{"plugins":[["@babel/plugin...."]]
}

3.6 配置 React 代码的打包

React 自带有 JSX 的语法,如果不进行非凡解决的话 webpack 是没方法辨认的,其实 Babel 外面就有工具能够帮忙咱们解析 React 种的 JSX 语法:

npm install --save-dev @babel/preset-react

而后再 presets 字段外面增加 @babel/preset-react 就能够了。

{
    presets:[
        [
            "@babel/preset-env",{
                targets:{chrome:'67',},
                useBuiltIns:'usage'
            }
        ],
        "@babel/preset-react"
    ]
}

留神:Babel 外面语法本义是有执行程序的,从下往上,从右往左,先转换 react 代码,而后把 ES6 转换为 ES5。重启 Devserver 即可

退出移动版