[toc]1.devServer不知道大家有没有发现前几课调试的一个繁琐的步骤,就是需要重新执行npm每次都运行test和packagebundle命令,然后手动打开dist目录下的index.html文件看效果。我们能否在src目录下的源代码发生变化时自动完成上述过程呢?1.1使用--watch参数第一种方法我们可以在webpack命令中加上--watch参数://package.json"scipts":{"watch":"webpack--watch"}它将监控并打包sourcecodeChanges,一旦源码发生变化,他就会重新打包。从而更新dist目录下的文件。这是第一个解决方法。不会帮我们启动服务器,也没办法做ajax调试。您必须手动刷新浏览器。1.2webpackDevServer上面的方法只能完成自动打包,但是如果要自动打开文件,模拟一些服务器特性,还是不够的。这里需要用到工具devServer,它是webpack启动的本地服务器,可以帮助我们监控源代码的变化,同时自动刷新浏览器。基本使用需要先安装:npminstallwebpack-dev-server-D然后配置://webpack.config.jsdevServer:{contentBase:'./dist'//serverrootpath}再加一个到package.json文件命令:"start":"webpack-dev-server"然后执行启动命令,日志提示我们在本地8080端口启动一个服务,可以访问,此时我们已经改了源码,也会帮我们自动刷新浏览器设备,所以可以很好的提高开发效率。现在我们发现没有dist目录。其实devServer本身还是会打包src,只是不会放在dist目录下而是打包进内存,这样打包速度会更快,不用担心。同时,与之前直接以服务器的形式打开本地index.html的方式相比,还有一个好处就是可以发送ajax请求,因为请求必须是在http协议的服务器上,而本地打开的是文件协议,那是肯定不行的。!并且在服务器打开的时候,从本地8080端口发送,一般不会有问题——大多数前端框架都是这样使用的。Otherconfigurationopen:这个配置可以让你执行命令后自动打开浏览器,自动显示界面。proxy:一般的前端框架有时会配置这个参数来进行接口代理,跨域请求访问8080的api端口会被转发到本地的3000端口。port:配置devServer启动的端口1.3实现一个类似的服务自己这里写一个简单的基于node的服务来模拟devserver的上述功能。首先,我们添加一个新的命令和对应的JS文件:"server":"nodeserver.js"接下来,我们需要完成这个server.js来帮助我们创建这样一个功能接近devServer的服务器。首先我们需要安装express或者koa来帮助你快速搭建服务器,同时你还需要监控webpack打包的变化,所以你还需要一个中间件:npminstallexpresswebpack-dev-middleware-D然后我们添加一个publicPath参数到webpack配置文件的output字段,这样所有packages引用的字段都会加上一个/根路径:output:{publicPath:'/',...}writeserver.jsconstexpress=require('express')constwebpack=require('webpack')constapp=express()//创建一个服务器实例//监听端口app.listen(3000,()=>{console.log('serverisrunning)})Then我们写webpack相关的逻辑,这里需要用到webpack和webpackDevMiddleware的一些API:constconfig=require('./webpack.config.js');//导入配置文件constcomplier=webpack(config);//使用webpackwithconfig随时编译代码。返回编译后的constapp=express()//创建一个服务器实例//使用complier//中间件帮助监控打包的源代码是否发生变化//只要文件发生变化,编译器就会重新运行,并且对应的打包输出内容为publicPathapp.use(webpackDevMiddleware(complier),{publicPath:config.output.publicPath})//监听端口app.listen(3000,()=>{console.log('serverisrunning)})现在你让complier编译器再执行一次,他会重新打包代码~然后我们改一下源码,可以看到控制台会输出打包信息。但是这里就是浏览器必须要手动刷新,需要配置很多东西,跟devServer一模一样。这里就不继续展开了,有兴趣的可以上网搜索学习资料。同时你也了解了node中也可以使用webpack,可以实现很多自定义的webpack扩展服务~2。HotModuleReplacement——模块热更新这里我们来看一个问题,当你修改源码的样式时,devServer会帮我们刷新浏览器,但是此时你对HTML没有任何更新(比如追加一个元素到页),您必须重新执行该操作。这里可以直接通过HMR修改CSS后更新页面的样式,不需要刷新。2.1HMR使用CSSdevServer:{...hot:true,//开启hmrhotOnly:true//即使hmr没有生效,我也不会让浏览器自动刷新,也不会做其他处理}这样,devServer配置完成,另外还需要引入一个webpack插件://webpack.config.jsconstwebpack=require('webpack')...plugins:[...newwebpack.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文件发生变化,会执行下面的函数--numberre-execute}2.2注意:与CSS不同的是,这个JS的HMR可以直接更新。CSS其实也需要写类似的逻辑,不过在css-loader中已经帮你处理好了。在vue等框架中,其实我们并没有写过类似上面的JSHMR代码,因为这些过程也是内置在vue-loader中的。React内部使用Babel进程来实现内置的HMR。但是要注意,如果要热更新一些比较偏远的文件,比如数据文件,默认的处理逻辑可能是没有的,需要自己手动写类似上面的代码,没有办法使用loader或者Bable过程。有兴趣的也可以继续在线学习HMR的底层原理。3.Babel这里我们将解释如何结合Babel和Webpack来处理我们的ES6语法3.1简介首先,我们创建一个新的index.js文件,它使用了一些ES6语法:constarr=[newPromise(()=>{}),新承诺(()=>{})];arr.map(item=>{console.log(item)})上面的index文件包含ES6语法,我们尝试使用webpack打包:npxwebpack这里没有用devServer打包,因为打包后的文件不会生成到目录,但是保存在内存中,所以看不到。直接用webpack打包就可以了。让我们看一下包生成的内容。导入索引中的内容几乎完好无损。至于前面的代码能不能运行,我们打开chrome看看(从devServer开始)。结果我们发现好像很正常,没有问题。这是因为较新版本的chrome浏览器本身已经实现了ES6的语法分析功能,但是如果我们使用低版本或者旧的IE浏览器,可能会报错。最后的原因是因为程序的兼容性差。我们希望webpack打包后能将ES6语法转换为ES5语法,让所有浏览器都能运行。3.2Babel介绍和使用Babel是一个常用的JS编译器。Babel可以将ES6语法转换为ES5语法。babel是配合webpack使用的,可以参考官网,有webpack的使用教程。这里需要安装一个babel-loader和babel的核心库@babel/core,只有这个库才能完成JS代码->AST->ES5代码的过程npminstall--save-devbabel-loader@babel/core然后按照上面的说明在config的rules中添加一条规则:rules:[{test:/\.js$/,exclude:/node_modules/,loader:"babel-loader"}]exclude表示如果你的js文件在node-moudles中没有使用babel-loader,因为这是第三方代码,基本上已经做了这个转义处理。然后还需要安装@babel/preset开启ES5的转换功能:npminstall@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"]}}]然后我们再做一个Package,查看main.js打包文件发现箭头函数变成了普通函数,let变成了var。此外,还可以通过配置target来说明需要对哪些浏览器进行转义。只有配置的浏览器版本低于您的版本。只有指定的版本号才会使用babel进行转换处理。一般都会这样配置,因为高版本的浏览器其实都是兼容ES6的。规则:[{test:/\.js$/,exclude:/node_modules/,loader:"babel-loader",options:{presets:[["@babel/preset-env",{targets:{edge:"17",chrome:"67",firefox:"60",safari:"11.1"}}]]}}]3.3babel/polyfill使用了,但是这个转换够了吗?那还不够。比如数组的map方法在低版本的浏览器中是不存在的,所以这里不仅需要preset-env做语法转换,还需要想办法在代码中添加缺失的变量或者函数。这里需要使用babel-polyfill:npminstall--save@babel/polyfill注意这里不要加-dev,因为代码运行的时候也需要这个。我们只需要在需要polyfill的地方import,这里我们把这个import放在业务代码的最前面index.js//index.jsimport"@babel/polyfill";constarr=[];arr.map(...)这种写法在这里其实不太好。其实在1中所有的polyfill函数都被打包了,你会发现构建后的包很大。这里我们不希望babel编写所有兼容的语法。比如我们只想要polyfillmap方法,否则包太大了。这里需要在webpack.config.js中写入配置,更改其中的presets字段,添加配置:rules:[{test:/\.js$/,exclude:/node_modules/,loader:"babel-loader",options:{presets:[["@babel/preset-env",{useBuiltIns:'usage'}]]}}]的意思是我做polyfill的时候,不是很常见的东西加,而是加看使用的是什么,然后打包:注意:如果你是开发类库或者第三方模块,不推荐使用这种方法,因为polyfill替换了全局兼容的方法,类库等使用这种方法污染地球环境。3.4babel/plugin-transform-runtime打包类库需要另行配置:这里我们需要安装两个模块,按照官网的说明。然后需要在webpack配置参数中添加一个插件: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写的,这时候参考文档再安装一个包:npminstall--save@babel/runtime-corejs2就可以了。如果是写业务代码,参考3.3使用polyfill。如果是写类库,参考上面的,避免污染全局环境。3.5解决选项配置项过多的问题线上业务可能会有很多选项配置。这时候我们可以在根目录下单独写一个.babelrc文件,把配置项复制进去://.babelrc{"plugins":[["@babel/plugin...."]]}3.6配置React代码的打包React自带JSX语法,webpack不做特殊处理无法识别。其实Babel中有工具可以帮助我们解析React类的JSX语法:npminstall--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
