• 如果您觉得本站非常有看点,那么赶紧使用Ctrl+D 收藏吧

Webpack的核心概念-devServer、HMR与Babel

互联网 diligentman 1周前 (10-17) 12次浏览

[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即可


喜欢 (0)