pomelovico / keep

A learning notebook
7 stars 0 forks source link

Webpack再学习 #19

Open pomelovico opened 6 years ago

pomelovico commented 6 years ago

本篇笔记主要是针对webpack的一些配置项的理解备注,学习自《深入浅出webpack》

module.export.context

webpack寻找相对路径资源的上下文的根目录,一般配置里很少会写

output

output.path

webpack进行打包后的资源存放的本地磁盘路径,必须是一个绝对路径,所以会使用nodejs的path模块进行处理

output.publicPath

用于配置发布到线上服务器的资源的URL前缀,默认为空,所以当不配置的时候,这些资源的路径就是相对路径。举个例子:

我们有如下css文件: 20181010101308

webpack配置文件的output.publicPath配置如下: tim20181010101404

打包,找到打包后的代码,可以看到,在自执行的函数里有一个__webpack_require__.p就是outputPath: tim 20181010101438

然后看我们的打包后的CSS文件: tim 20181010101538 可以看到,url设置的背景图片被当作一个模块引入了,通过模块ID在代码里寻找到该模块: tim 20181010101612 ,可以看到,图片资源的路径就是以publicPath设置的值为前缀从而拼接得到的url,图片资源名由于在loader中没有设置,所以是默认的随机码

此外,有尝试在css的background属性中设置url为外链,打包后的文件中可以看到webpack并不会对外链做处理。

再者,webpack配置的publicPath是针对所有的资源所设置的,在一些loader中也可以进行自行设置publicPath,以此可以覆盖掉默认的publicPath

由上述实践总结到:publicPath的作用就是指定所有会通过url来引用资源的url前缀

output.libraryTarget

output.library

两者结合使用,后者指定导出库的名字,前者指定webpack最终导出的库的形式:

pomelovico commented 6 years ago

Babel (babel-loader)

Babel用于将一些新的ES语法特性代码转换为现主流浏览器能够识别的语法代码。

babel是基于插件和预设的形式来使用的,使用babel的时候,需要在当前运行目录下配置.babelrc文件,该文件是一个标准的json格式文件,用于指定babel在进行转译时采用什么插件以及代码转译的程度。

结合Webpack使用的时候,可以不编写.babelrc文件,这时候就需要在webpack的配置文件中的module项里,对babel-loaderoptions进行配置,内容和.babelrc一致

plugins

plugins 属性告诉Babel 要使用哪些插件,这些插件可以控制如何转换代码 如:transform-runtime

插件包名统一以babel-plugin-为前缀,如babel-plugin-transform-runtime

presets

presets 属性告诉Babel 要转换的源码使用了哪些新的语法特性,一个Presets 对一组新语法的特性提供了支持,多个Presets 可以叠加。Presets 其实是一组Plugins 的集合,每个Plugin 完成一个新语法的转换工作。Presets 是按照ECMAScript 草案来组织的

如:envstage-0,stage-1,es2015,react

语法包名统一以babel-preset-为前缀,如babel-preset-react

plugin与presets对每一项都有独立的配置,具体要参考对应的文档

pomelovico commented 6 years ago

对Webpack打包原理的理解

Webpack整体构建流程大致如下:

  1. 启动构建: 分命令行启动和API启动,从package.json中可以看到,命令行启动是调用了webpack/bin/webpack.js,执行文件读取外部配置,然后使用API形式调用。所以最终调用形式都是API接口形式
    
    const webpack = require('webpack');

//得到编译器对象 const compiler = webpack(config); //执行, compiler.run((err,stats)=>{});

//也可以直接在webpack函数里传入回调函数触发执行: webpack(config,(err,stats)=>{ /有此回调函数时,会自动调用compiler.run(callback)/ })


2. **初始化插件:** Webpack整体构建流程是基于 **事件广播+插件**形式完成的,在执行`compiler.run()`之前,会在compiler上注册所有的插件,插件分内部插件和外部插件,内部插件就是属于Webpack自身构建过程中会使用的插件,外部插件就是我们在`config.plugins`数组中实例化传进去的插件对象。所有的插件都遵照一定规范书写,如下:
```javascript
class MyPlugin {
    apply(compiler){
          /**
          complier的plugin方法会为每一个事件类型维护一个数组,
         用以存储插件传递过来的回调函数,
         最终所有的事件名和回调队列以键值对的形式保存在了`compiler._plugins`对象上
         */
        compiler.plugin('webpack生命周期事件',(compilation.callback)=>{
            /**
             Webpack在触发生命事件时,就会依次调用注册在该事件数组里的回调,
             同时将Webpack的compilation对象传入,
             该对象已经保存了当前处理过程中的资源等信息,供插件进行处理
            */
        })
        compiler.plugin('webpack另一个生命周期事件',(compliation.callback)=>{/*...*/})
    }
}

compiler注册插件形式如下:

//apply方法继承自Tapable类,该方法遍历插件对象数组,然后调用每一个plugin的`apply`方法
compiler.apply([plugin1,plugin2,/*...*/]);

compiler触发相应的生命周期插件:

compiler.applyPlugins(name);
//根据Compiler继承的Tapable类接口来看,
//不止有一种调用方法,但都以`apply`开头,用于不同情形,比如有异步等等
  1. 编译阶段: 从入口文件出发,对每个模块文件调用相应的Loader进行翻译转换,然后递归的处理该模块所依赖的其他模块。当所有模块都被找到并编译好之后,Webpack会将其组合,转换成文件。

最重要的,在整个过程中,Webpack会不断的触发各种事件节点,让各类插件参与到当前的构建进度中,从而完成整个构建过程

webpack生命周期

webpack的生命周期十分复杂而且繁多,囊括了编译阶段前的准备,编译过程,编译结束,资源输出,输出资源优化等等,编写插件时只需找到对应的生命周期节点进行注册,就能做相应的处理

小结

初步看了下webpack的源码,打断点没有走完全程,在并行的控制流代码里彻底混乱,所有代码基于Tapable这个事件发布订阅类,看着令人头大......

但是只要理解清楚Tapable中那几个apply*函数的作用就可以理解webpack为啥会调用不同的函数了,比如有同步顺序执行异步顺序执行异步并行执行等区别,大多数方法都是同类方法的变体,参数不一样而已