Skip to content

手写webpack

打包库的基础设置

webpack_papck

npm link 把打包工具的包链接到全局,建立链接关系。

webpack_demo

测试webpack工具的功能。

初始化项目

  npm init

  npm i webpack webpack-cli

运行webpack

  npx webpack

本地库链接

  npm link webpack_pack

执行自定义包

  npx pack

编译过程、依赖关系

AST递归解析、打包文件

使用AST的工具

webpack、Babel、ESLint、Rollup

AST:Abstract Syntax Tree 抽象语法树

JS源代码对应的一种树状的数据结构。

使用AST时的四个步骤:

        Parse 解析  Source code -> AST  
Input 

        Traverse 遍历 


        Manipulate 修改,操作,转换
Output  

        Generate code 编译成正常代码

免费AST解析(astexplorer.net),可以用它查看解析后的AST语法树的格式。

编译AST树

npm i babylon -D

遍历AST树

npm i @babel/traverse -D

操作AST树

npm i @babel/types -D

将AST转换成JS代码

npm i @babel/generator -D

ejs 模板替换

npm i ejs -D

添加loader、plugin

npm i less -D

npm i tapable -D

loader的使用、babel-loader

webpack中loader相关的概念。

npm i --save-dev webpack webpack-cli

使用自定义loader的方式

1. 绝对路径引用

  module: {
    rules: [
      {
        test: /\.js$/,
        use: path.resolve(__dirname, 'loaders', 'loader-one')
      }
    ]
  }

2. 别名的方式

  resolveLoader: {
    alias: {
      'loader-one': path.resolve(__dirname, 'loaders', 'loader-one')
    }
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        use: 'loader-one'
      }
    ]
  }

3. 配置模块查找范围

  resolveLoader: {
    modules: ['node_modules', path.resolve(__dirname, 'loaders')]
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        use: 'loader-one'
      }
    ]
  }

多个loader的使用

  1. 字符串的方式

    rules: [
      {
        test: /\.js$/,
        use: [
          'loader-three',
          'loader-two',
          'loader-one'
        ]
      }
    ]

  数组中loader是自右向左执行的。

  2. 对象的方式

    rules: [
      {
        test: /\.js$/,
        use: 'loader-three',
      },
      {
        test: /\.js$/,
        use: 'loader-two'
      },
      {
        test: /\.js$/,
        use: 'loader-one'
      }
    ]

    rules中的解析顺序自下向上执行的。

  3. 自定义执行顺序

    webpack中loader的种类。

    pre normal inline post

    rules: [
      {
        test: /\.js$/,
        use: 'loader-one',
        enforce: 'pre'
      },
      {
        test: /\.js$/,
        use: 'loader-two'
      },
      {
        test: /\.js$/,
        use: 'loader-three',
        enforce: 'post'
      }
    ]

inline loader是什么?

  js文件使用的loader。
            
  let str = require('inline-loader!./a.js');
  console.log(str);

  inline loader的3种使用语法

  1. loader前添加 !

    设置 ! 后,所有的normal loader都不会执行。

    require('!inline-loader!./a.js');

  2. loader前添加 !!

    设置 !! 后,所有的pre、normal、post loader都不会执行。

  3. loader前添加 -!

    设置 -! 后,所有的pre、normal loader不会执行。

loader执行的阶段

  1. pitching 阶段

  2. normal 阶段

  use: [
    'a-loader',
    'b-loader',
    'c-loader'
  ]

  pitch 阶段

    a -> b -> c

  资源作为依赖将要被loader处理。

  normal 阶段

    c -> b -> a

  这样的执行顺序的前提条件是loader没有返回值。

  如果b-loader存在返回值,就不再执行c-loader的pitch阶段,可以起到阻断作用。
  loader执行时,也只会执行c-loader。


  代码演示:

    function loader (sourceCode) {
      console.log('loader one!');
      return sourceCode;
    }

    loader.pitch = function () {
      console.log('loader one pitch phase!');
    }

    module.exports = loader;


    function loader (sourceCode) {
      console.log('loader one!');
      return sourceCode;
    }

    loader.pitch = function () {
      console.log('loader one pitch phase!');
    }

    module.exports = loader;


    function loader (sourceCode) {
      console.log('loader three!');
      return sourceCode;
    }

    loader.pitch = function () {
      console.log('loader three pitch phase!');
    }

    module.exports = loader;


    // loader three pitch phase!
    // loader two pitch phase!
    // loader one pitch phase!
    // loader one!
    // loader two!
    // loader three!


    设置阻断

      loader.pitch = function () {
        console.log('loader two pitch phase!');
        return 'hello';
      }

      // loader three pitch phase!
      // loader two pitch phase!
      // loader three!

  loader的特性

    每个loader都只会完成一个任务,有利更加好的组合loader,让loader能够实现链式调用;
    loader是一个单独的模块;
    loader是无状态的,不应该存在条件的状态,应该是纯函数,保证代码是可预测的;

实现 babel-loader

  使用 babel-loader 时需要安装

    npm i -D babel-loader @babel/core @babel/preset-env

  不需要安装babel-loader,只需要安装@babel/core @babel/preset-env。

    npm i @babel/core @babel/preset-env --save-dev

  使用webpack loaders的工具函数

    npm i loader-utils --save-dev

loader API、处理图片

自定义banner-loader

  检查options结构的模块  schema-utils

    npm i schema-utils --save-dev

  webpack监听文件变化

    watch: true

    可以配合wepack API搭配使用
      
      this.addDependency(filename);

  缓存相关API

    this.cacheable(false); // 不需要缓存

    this.cacheable && this.cacheable(); // 缓存,默认写法

file-loader 实现

  按照图片生成MD5,提交到dist目录,返回转换后的路径

  const loaderUtils = require('loader-utils');

  function loader (sourceCode) {
    const filename = loaderUtils.interpolateName(this, '[hash].[ext]', {
      content: sourceCode
    });

    this.emitFile(filename, sourceCode);

    console.log(filename);

    return `module.exports = "${filename}"`;
  }

  // 二进制数据设置
  loader.raw = true;

  module.exports = loader;

url-loader 实现

  图片格式处理 mime

    npm i mime --save-dev

样式相关的loader

less-loader、css-loader、style-loader 实现

  npm i less --save-dev

处理正常文件 less-loader、style-loader

  body {
    background-color: @background-color;
  }

处理添加url的文件 css-loader、style-loader

  body {
    background: url('../images/logo.jpg');
    background-color: @background-color;
  }

同步异步插件、读取资源插件

npm i webpack webpack-cli --save-dev

同步插件

  class DonePlugin {
    apply (compiler) {
      compiler.hooks.done.tap('DonePlugin', (status) => {
        console.log('编译完成.');
      });
    }
  }

异步插件

  1. 回调函数的方式

    apply (compiler) {
      compiler.hooks.emit.tapAsync('AsyncPlugin', (compilation, cb) => {
        setTimeout(() => {
          console.log('emit done.');
          cb();
        }, 3000)
      });
    }

  2. promise的方式
    
    apply (compiler) {
      compiler.hooks.emit.tapPromise('AsyncPlugin', (compilation) => {
        return new Promise((resolve, reject) => {
          setTimeout(() => {
            console.log('emit done.');
            resolve();
          }, 3000);
        });
      });
    }

编写资源读取插件

  用于记录 dist 目录下的文件大小及名称。

  使用WebpackHtmlPlugin

    npm i html-webpack-plugin --save-dev

内联插件、打包自动上传插件

npm i --save-dev mini-css-extract-plugin css-loader

内联插件

  将html-webpack-plugin引入的文件,设置成内联。
  减少JS文件和样式文件的请求。

打包自动上传插件

  安装七牛模块

    npm i qiniu --save-dev