基础

webpack4.0(一):基础

概念

context

基础目录,绝对路径,用户设置从配置文件中解析入口文件和加载器。
如:

module.exports = {
    context:path.resolve(__dirname,'js),
  entry:'./main.js'
}

等同于:

module.exports = {
  entry:'./js/main.js
}

context配置可以不要,默认为当前目录下查找。但是如果要使用的话,建议添加第二个参数,用于告诉webpack是从当前目录下哪个文件夹中查找。

entry

webpack中有多种定义入口的形式。

单入口写法
const config = {
    entry:'./src/main.js'
};
module.exports = config;
对象语法
const config = {
    entry:{
    main:'./src/main.js',
    venders:'./src/venders.js'
  }
};
module.exports = config;

output

配置output可以指定webpack如何向硬盘中写入,注意的是:webpack可以配置多个入口文件,但是只有一个输出配置。

在webpack中,output最低的配置要求就是:

  • filename:用于指定输出的文件名
  • path:用于指定输出文件的路径
import path from 'path';
const config = {
  output:{
    filename:'[name].js',
    path:path.resolve(__dirname,'dist')
  }
};
module.exports = config;

mode

mode模块用于告诉webpack使用相应的模式进行优化。

  • development:开发环境。会将 process.env.NODE_ENV 的值设为 development。启用 NamedChunksPluginNamedModulesPlugin
  • production:生产环境。会将 process.env.NODE_ENV 的值设为 production。启用 FlagDependencyUsagePlugin, FlagIncludedChunksPlugin, ModuleConcatenationPlugin, NoEmitOnErrorsPlugin, OccurrenceOrderPlugin, SideEffectsFlagPluginUglifyJsPlugin
module.exports = {
  mode: 'production'
};

loader

我们都知道webpack可以对资源进行打包的。但是webpack只能处理js模块,对于其他模块的处理,就需要用loader对模块的源代码进行转换。loader可以使你在import或者“加载”模块的时候,进行预编译。loader 可以将文件从不同的语言(如 TypeScript)转换为 JavaScript,或将内联图像转换为 data URL。loader 甚至允许你直接在 JavaScript 模块中 import CSS文件!

module.exports = {
  module:{
    rules:[
      {
        test:/\.css$/,
        use:['style-loader','css-loader']
      }
    ]
  }
}
编写一个loader

loader工具库:loader-utils、配合schema-utils

import { getOptions } from 'loader-utils';
import validateOptions from 'schema-utils';
//loader的API请参考:https://webpack.docschina.org/api/loaders/

所谓 loader 只是一个导出为函数的 JavaScript 模块。

  • 新建一个js文件:
//loader-utils 它提供了许多有用的工具,但最常用的一种工具是获取传递给 loader 的选项
const loadersUtils = require('loader-utils');
//这是最简单的一个loader
module.exports = function(content) {
  //content是文件内容
  const options = loadersUtils.getOptions(this)
  console.log(content, '--------content');
  console.log(options, '--------options');

   //同步loader
    // return '{};' + content
    /**
     * this.callback() 参数:
     * error:Error | null,当loader出错时向外跑出一个Error
     * content:经过loader处理过的内容
     * sourceMap:为方便调试生成的编译后内容的source map
     * ast:本次编译生成的AST静态语法树,之后执行的loader可以直接使用这个AST,可以省去重复生成AST的过程
     * 
     */
    // this.callback(null, '{};' + content)
   //异步 使用promise
    function timeout(time) {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve(content)
            }, time)
        })
    };
       // let data = await timeout(1000);
    // return data
    const callback = this.async()
    timeout(1000).then(data => {
        callback(null, data)
    })
}
  • webpack.config.js中使用该loader:
rules: [{
  test: /\.js$/,
  exclude: /node_modules/,
  use: {
    loader: resolve('./loaders/index.js'),
    options: {
        test: 1
    }
  }
}]
  • 如果有多个loader,可以在webpack.config.js中这样配置:
resolveLoader:{
    modules:[
        'node_modules',
        path.resolve(__dirname,'loaders')//loaders代表你存放loaders的文件夹
    ]
}

plugins

webpack事件流:

  • 首先,webpack会读取你在命令行和webpack.config.js的配置,初始化本次构建的参数,并且执行配置中的插件实例化,生产compiler传入apply方法中,为webpack事件流挂上自定义钩子。
  • 接下来到了解析入口文件阶段,webpack会根据entry依次读取入口文件。
  • webpack开始编译(conpilation),对于每一个入口文件,都会先根据用户自定义的loader对文件内容进行编译(buildModule),我们可以从传入事件回调的compliation上拿到moduleresourceloader等信息。之后,再将编译好的文件内容使用acorn解析生成AST(静态语法树,normalModuleLoader)。分析文件的依赖关系,并依次拉取依赖模块,并重复以上过程。最后将require语法替换为__webpack_require__来模拟模块化操作。
  • emit阶段,所有文件的编译及转化都已经完成,包含了最终输出的资源,我们可以在传入事件回调的compilation.assets 上拿到所需数据,其中包括即将输出的资源、代码块Chunk等等信息。

我们将webpack事件流理解为webpack构建过程中的一系列事件,他们分别表示着不同的构建周期和状态,我们可以像在浏览器上监听click事件一样监听事件流上的事件,并且为它们挂载事件回调。我们也可以自定义事件并在合适时机进行广播,这一切都是使用了webpack自带的模块 Tapable 进行管理的。我们不需要自行安装 Tapable ,在webpack被安装的同时它也会一并被安装,如需使用,我们只需要在文件里直接 require 即可。

Tapable的原理其实就是我们在前端进阶过程中都会经历的EventEmit,通过发布者-订阅者模式实现,它的部分核心代码可以概括成下面这样:

class SyncHook{
    constructor(){
        this.hooks = [];
    }

    // 订阅事件
    tap(name, fn){
        this.hooks.push(fn);
    }

    // 发布
    call(){
        this.hooks.forEach(hook => hook(...arguments));
    }
}

具体请参考:https://juejin.im/post/5abf33f16fb9a028e46ec352

alias

resolve.alias 是通过别名来将原导入路径映射成一个新的导入路径。比如如下配置:

module.exports = {
  entry: './js/main.js',
  output: {
    filename: 'bundle.js',
    // 将输出的文件都放在dist目录下
    path: path.resolve(__dirname, 'dist')
  },
  resolve: {
    alias: {
      components: './src/components'
    }
  }
}

如上代码配置,当我通过 import xxx from ‘components/xxx’ 导入时,实际上被alias替换成了
import xxx from ‘./src/components/xxx’;

extensions

在使用 import 导入文件时,有时候没有带入文件的后缀名,webpack会自动带上后缀去访问文件是否存在,那么默认的后缀名为
[‘.js’, ‘.json’]; 即:
extensions: [‘.js’, ‘.json’]; 如果我们想自己配置.vue后缀名,防止在导入文件时候不需要加上后缀;我们可以使用webpack做如下配置:

module.exports = {
  entry: './js/main.js',
  output: {
    filename: 'bundle.js',
    // 将输出的文件都放在dist目录下
    path: path.resolve(__dirname, 'dist')
  },
  resolve: {
    alias: {
      components: './src/components'
    },
    extensions: ['.js', '.vue'];
  }
}

Externals

Externals 用来告诉webpack在构建代码时使用了不处理应用的某些依赖库,依然可以在代码中通过AMD,CMD或window全局方式访问。
什么意思呢?就是说我们在html页面中引入了jquery源文件后,比如如下代码:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
  <script src="http://code.jquery.com/jquery-1.12.0.min.js"></script>
  <link href="dist/main.css" rel="stylesheet" type="text/css" />
</head>
<body>
  <div id="app"></div>
  <script src="dist/bundle.js"></script>
</body>
</html>

但是我们想在模块化的源代码里导入和使用jquery,首先我们肯定需要安装下 jquery依赖包,npm install –save jquery, 然后我们编写如下代码进行使用:

const $ = require('jquery');
console.log($);

构建打包后我们会发现输出的文件里面包含jquery的内容,这导致了jquery库引入了两次,并且导致了bundle.js文件变得更大,浪费加载流量。因此 Externals 配置项就是来解决这个问题的。
通过externals 可以告诉webpack在javascript运行环境中已经内置了哪些全局变量,不用将这些代码打包到代码里面去。
因此我们可以做如下配置:

module.export = {
  externals: {
    jquery: 'jQuery'
  }
};

什么是AST?

请参考:https://segmentfault.com/a/1190000016231512#comment-area

转换成AST的目的就是将我们书写的字符串文件转换成计算机更容易识别的数据结构,这样更容易提取其中的关键信息,而这棵树在计算机上的表现形式,其实就是一个单纯的Object。

  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!