24 个实例入门并控制「Webpack4」(三)

24 个实例入门并掌握「Webpack4」(二) 后续:

  1. PWA 设置
  2. TypeScript 设置
  3. Eslint 设置
  4. 运用 DLLPlugin 加速打包速率
  5. 多页面打包设置
  6. 编写 loader
  7. 编写 plugin
  8. 编写 Bundle

十七、PWA 设置

demo17 源码地点

本节运用 demo15 的代码为基本

我们来模仿日常平凡开辟中,将打包完的代码防备到效劳器上的操纵,起首打包代码 npm run build

然后装置一个插件 npm i http-server -D

在 package.json 中设置一个 script 敕令

{
  "scripts": {
    "start": "http-server dist",
    "dev": "webpack-dev-server --open --config ./build/webpack.dev.conf.js",
    "build": "webpack --config ./build/webpack.prod.conf.js"
  }
}

运转 npm run start

《24 个实例入门并控制「Webpack4」(三)》

如今就起了一个效劳,端口是 8080,如今接见 http://127.0.0.1:8080 就可以看到效果了

假如你有在跑别的项目,端口也是 8080,端口就争执,记得先封闭其他项目的 8080 端口,再
npm run start

我们按 ctrl + c 封闭 http-server 来模仿效劳器挂了的场景,再接见 http://127.0.0.1:8080 就会是如许

《24 个实例入门并控制「Webpack4」(三)》

页面接见不到了,因为我们效劳器挂了,PWA 是什么手艺呢,它可以在你第一次接见胜利的时候,做一个缓存,当效劳器挂了今后,你依旧可以接见这个网页

起首装置一个插件:workbox-webpack-plugin

npm i workbox-webpack-plugin -D

只需要上线的代码,才须要做 PWA 的处置惩罚,翻开 webpack.prod.conf.js

const WorkboxPlugin = require('workbox-webpack-plugin') // 引入 PWA 插件

const prodConfig = {
  plugins: [
    // 设置 PWA
    new WorkboxPlugin.GenerateSW({
      clientsClaim: true,
      skipWaiting: true
    })
  ]
}

从新打包,在 dist 目次下会多出 service-worker.jsprecache-manifest.js 两个文件,经由过程这两个文件就可以使我们的网页支撑 PWA 手艺,service-worker.js 可以明白为另类的缓存

《24 个实例入门并控制「Webpack4」(三)》

还须要去营业代码中运用 service-worker

在 app.js 中加上以下代码

// 推断该浏览器支不支撑 serviceWorker
if ('serviceWorker' in navigator) {
  window.addEventListener('load', () => {
    navigator.serviceWorker
      .register('/service-worker.js')
      .then(registration => {
        console.log('service-worker registed')
      })
      .catch(error => {
        console.log('service-worker registed error')
      })
  })
}

从新打包,然后运转 npm run start 来模仿效劳器上的操纵,最好用无痕情势翻开 http://127.0.0.1:8080 ,翻开掌握台

《24 个实例入门并控制「Webpack4」(三)》

如今文件已被缓存住了,再按 ctrl + c 封闭效劳,再次革新页面也照样能显现的

TypeScript设置

demo18 源码地点

TypeScript 是 JavaScript 范例的超集,它可以编译成纯 JavaScript

新建文件夹,npm init -ynpm i webpack webpack-cli -D,新建 src 目次,建立 index.ts 文件,这段代码在浏览器上是运转不了的,须要我们打包编译,转成 js

class Greeter {
  greeting: string
  constructor(message: string) {
    this.greeting = message
  }
  greet() {
    return 'Hello, ' + this.greeting
  }
}

let greeter = new Greeter('world')

alert(greeter.greet())
npm i ts-loader typescript -D

新建 webpack.config.js 并设置

const path = require('path')

module.exports = {
  mode: 'production',
  entry: './src/index.ts',
  module: {
    rules: [
      {
        test: /\.ts?$/,
        use: 'ts-loader',
        exclude: /node_modules/
      }
    ]
  },
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  }
}

在 package.json 中设置 script

{
  "scripts": {
    "build": "webpack"
  }
}

运转 npm ruh build,报错了,缺乏 tsconfig.json 文件

《24 个实例入门并控制「Webpack4」(三)》

当打包 typescript 文件的时候,须要在项目的根目次下建立一个 tsconfig.json 文件

以下为简朴设置,更多概况看官网

{
  "compileerOptions": {
    "outDir": "./dist", // 写不写都行
    "module": "es6", // 用 es6 模块引入 import
    "target": "es5", // 打包成 es5
    "allowJs": true // 许可在 ts 中也能引入 js 的文件
  }
}

再次打包,翻开 bundle.js 文件,将代码悉数拷贝到浏览器掌握台上,运用这段代码,可以看到弹窗涌现 Hello,world,申明 ts 编译打包胜利

《24 个实例入门并控制「Webpack4」(三)》

引入第三方库

npm i lodash
import _ from 'lodash'

class Greeter {
  greeting: string
  constructor(message: string) {
    this.greeting = message
  }
  greet() {
    return _.join()
  }
}

let greeter = new Greeter('world')

alert(greeter.greet())

lodash 的 join 要领须要我们通报参数,然则如今我们什么都没传,也没有报错,我们运用 typescript 就是为了范例搜检,在引入第三方库的时候也能云云,但是如今缺并没有报错或许提醒

我们还要装置一个 lodash 的 typescript 插件,如许就可以辨认 lodash 要领中的参数,一旦运用的不对就会报错出来

npm i @types/lodash -D

装置完今后可以发明下划线 _ 报错了

《24 个实例入门并控制「Webpack4」(三)》

须要改成 import * as _ from 'lodash',将 join 要领通报的参数删除,还可以发明 join 要领的报错,这就表现了 typescript 的上风,同理,引入 jQuery 也要引入一个 jQuery 对应的范例插件

《24 个实例入门并控制「Webpack4」(三)》

怎样晓得运用的库须要装置对应的范例插件呢?

翻开TypeSearch,在这里对应的去搜刮你想用的库有无范例插件,假如有只须要 npm i @types/jquery -D 即可

《24 个实例入门并控制「Webpack4」(三)》

十九、Eslint 设置

demo19 源码地点

建立一个空文件夹,npm init -ynpm webpack webpack-cli -D 起手式,今后装置 eslint 依靠

npm i eslint -D

运用 npx 运转此项目中的 eslint 来初始化设置,npx eslint --init

《24 个实例入门并控制「Webpack4」(三)》

《24 个实例入门并控制「Webpack4」(三)》

这里会有挑选是 React/Vue/JavaScript,我们一致都先挑选 JavaScript。选完后会在项目的根目次下新建一个 .eslintrc.js 设置文件

module.exports = {
  env: {
    browser: true,
    es6: true
  },
  extends: 'eslint:recommended',
  globals: {
    Atomics: 'readonly',
    SharedArrayBuffer: 'readonly'
  },
  parserOptions: {
    ecmaVersion: 2018,
    sourceType: 'module'
  },
  rules: {}
}

内里就是 eslint 的一些范例,也可以定义一些划定规矩,详细看 eslint 设置划定规矩

《24 个实例入门并控制「Webpack4」(三)》

在 index.js 中随意写点代码来测试一下 eslint

《24 个实例入门并控制「Webpack4」(三)》

eslint 报错提醒,变量定义后却没有运用,假如在编辑器里没涌现报错提醒,须要在 vscode 里先装置一个 eslint 扩大,它会依据你当前目次的下的 .eslintrc.js 文件来做作为校验的划定规矩

《24 个实例入门并控制「Webpack4」(三)》

也可以经由过程敕令行的情势,让 eslint 校验全部 src 目次下的文件

《24 个实例入门并控制「Webpack4」(三)》

假如你以为某个划定规矩很贫苦,想屏蔽掉某个划定规矩的时候,可以如许,依据 eslint 的报错提醒,比方上面的 no-unused-vars,将这条划定规矩复制一下,在 .eslintrc.js 中的 rules 里设置一下,"no-unused-vars": 0,0 示意禁用,保留后,就不会报错了,然则这类体式格局是适用于全局的设置,假如你只想在某一行代码上屏蔽掉 eslint 校验,可以如许做

/* eslint-disable no-unused-vars */
let a = '1'

这个 eslint 的 vscode 扩大和 webpack 是没有什么关联的,我们如今要讲的是怎样在 webpack 里运用 eslint,起首装置一个插件

npm i eslint-loader -D

在 webpack.config.js 中举行设置

/* eslint-disable no-undef */
// eslint-disable-next-line no-undef
const path = require('path')

module.exports = {
  mode: 'production',
  entry: {
    app: './src/index.js' // 须要打包的文件进口
  },
  module: {
    rules: [
      {
        test: /\.js$/, // 运用正则来婚配 js 文件
        exclude: /nodes_modules/, // 消除依靠包文件夹
        use: {
          loader: 'eslint-loader' // 运用 eslint-loader
        }
      }
    ]
  },
  output: {
    // eslint-disable-next-line no-undef
    publicPath: __dirname + '/dist/', // js 援用的途径或许 CDN 地点
    // eslint-disable-next-line no-undef
    path: path.resolve(__dirname, 'dist'), // 打包文件的输出目次
    filename: 'bundle.js' // 打包后临盆的 js 文件
  }
}

因为 webpack 设置文件也会被 eslint 校验,这里我先写上解释,封闭校验

假如你有运用 babel-loader 来转译,则 loader 应当这么写

loader: ['babel-loader', 'eslint-loader']

rules 的实行递次是从右往左,从下往上的,先经由 eslint 校验推断代码是不是相符范例,然后再经由过程 babel 来做转移

设置完 webpack.config.js,我们将 index.js 复原回之前报错的状况,不要运用解释封闭校验,然后运转打包敕令,记得去 package.json 设置 script

《24 个实例入门并控制「Webpack4」(三)》

会在打包的时候,提醒代码不合格,不仅仅是临盆环境,开辟环境也可以设置,可以将 eslint-loader 设置到 webpack 的大众模块中,如许更有利于我们搜检代码范例

如:设置 fix 为 true,它会帮你自动修复一些毛病,不能自动修复的,照样须要你本身手动修复

{
 loader: 'eslint-loader', // 运用 eslint-loader
  options: {
    fix: true
  }
}

关于 eslint-loader,webpack 的官网也给出了设置,感兴趣的朋侪本身去看一看

二十、运用 DLLPlugin 加速打包速率

demo20 源码地点

本节运用 demo15 的代码为基本

我们先装置一个 lodash 插件 npm i lodash,并在 app.js 文件中写入

import _ from 'lodash'
console.log(_.join(['hello', 'world'], '-'))

在 build 文件夹下新建 webpack.dll.js 文件

const path = require('path')

module.exports = {
  mode: 'production',
  entry: {
    vendors: ['lodash', 'jquery']
  },
  output: {
    filename: '[name].dll.js',
    path: path.resolve(__dirname, '../dll'),
    library: '[name]'
  }
}

这里运用 library,遗忘的朋侪可以回忆一下第十六节,自定义函数库里的内容,定义了 library 就相当于挂载了这个全局变量,只需在掌握台输入全局变量的称号就可以够显现内里的内容,比方这里我们是 library: '[name]' 对应的 name 就是我们在 entry 里定义的 vendors

在 package.json 中的 script 再新增一个敕令

{
  "scripts": {
    "dev": "webpack-dev-server --open --config ./build/webpack.dev.conf.js",
    "build": "webpack --config ./build/webpack.prod.conf.js",
    "build:dll": "webpack --config ./build/webpack.dll.js"
  }
}

运转 npm run build:dll,会天生 dll 文件夹,而且文件为 vendors.dll.js

《24 个实例入门并控制「Webpack4」(三)》

翻开文件可以发明 lodash 已被打包到了 dll 文件中

《24 个实例入门并控制「Webpack4」(三)》

那我们要怎样运用这个 vendors.dll.js 文件呢

须要再装置一个依靠 npm i add-asset-html-webpack-plugin,它会将我们打包后的 dll.js 文件注入到我们天生的 index.html 中

在 webpack.base.conf.js 文件中引入

const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin')

module.exports = {
  plugins: [
    new AddAssetHtmlWebpackPlugin({
      filepath: path.resolve(__dirname, '../dll/vendors.dll.js') // 对应的 dll 文件途径
    })
  ]
}

运用 npm run dev 来翻开网页

《24 个实例入门并控制「Webpack4」(三)》

如今我们已把第三方模块零丁打包成了 dll 文件,并运用

然则如今运用第三方模块的时候,要用 dll 文件,而不是运用 /node_modules/ 中的库,继承来修正 webpack.dll.js 设置

const path = require('path')
const webpack = require('webpack')

module.exports = {
  mode: 'production',
  entry: {
    vendors: ['lodash', 'jquery']
  },
  output: {
    filename: '[name].dll.js',
    path: path.resolve(__dirname, '../dll'),
    library: '[name]'
  },
  plugins: [
    new webpack.DllPlugin({
      name: '[name]',
      // 用这个插件来剖析打包后的这个库,把库里的第三方映照关联放在了这个 json 的文件下,这个文件在 dll 目次下
      path: path.resolve(__dirname, '../dll/[name].manifest.json')
    })
  ]
}

保留后从新打包 dll,npm run build:dll

《24 个实例入门并控制「Webpack4」(三)》

修正 webpack.base.conf.js 文件,增添 webpack.DllReferencePlugin 插件

module.exports = {
  plugins: [
    // 引入我们打包后的映照文件
    new webpack.DllReferencePlugin({
      manifest: path.resolve(__dirname, '../dll/vendors.manifest.json')
    })
  ]
}

今后再 webpack 打包的时候,就可以够连系之前的全局变量 vendors 和 这个新天生的 vendors.manifest.json 映照文件,然厥后对我们的源代码举行剖析,一旦剖析出运用第三方库是在 vendors.dll.js 里,就会去运用 vendors.dll.js,不会去运用 /node_modules/ 里的第三方库了

再次打包 npm run build,可以把 webpack.DllReferencePlugin 模块解释后再打包对比一下

解释前 4000ms 摆布,解释后 4300ms 摆布,虽然只是快了 300ms,然则我们现在只是实验性的 demo,现实项目中,比方拿 vue 来讲,vue,vue-router,vuex,element-ui,axios 等第三方库都可以打包到 dll.js 里,那个时候的打包速率就可以提拔很多了

还可以继承拆分,修正 webpack.dll.js 文件

const path = require('path')
const webpack = require('webpack')

module.exports = {
  mode: 'production',
  entry: {
    lodash: ['lodash'],
    jquery: ['jquery']
  },
  output: {
    filename: '[name].dll.js',
    path: path.resolve(__dirname, '../dll'),
    library: '[name]'
  },
  plugins: [
    new webpack.DllPlugin({
      name: '[name]',
      path: path.resolve(__dirname, '../dll/[name].manifest.json') // 用这个插件来剖析打包后的这个库,把库里的第三方映照关联放在了这个 json 的文件下,这个文件在 dll 目次下
    })
  ]
}

运转 npm run build:dll

《24 个实例入门并控制「Webpack4」(三)》

可以把之前打包的 vendors.dll.jsvendors.manifest.json 映照文件给删撤除

然后再修正 webpack.base.conf.js

module.exports = {
  plugins: [
    new AddAssetHtmlWebpackPlugin({
      filepath: path.resolve(__dirname, '../dll/lodash.dll.js')
    }),
    new AddAssetHtmlWebpackPlugin({
      filepath: path.resolve(__dirname, '../dll/jquery.dll.js')
    }),
    new webpack.DllReferencePlugin({
      manifest: path.resolve(__dirname, '../dll/lodash.manifest.json')
    }),
    new webpack.DllReferencePlugin({
      manifest: path.resolve(__dirname, '../dll/jquery.manifest.json')
    })
  ]
}

保留后运转 npm run dev,看看能不能胜利运转

《24 个实例入门并控制「Webpack4」(三)》

这还只是拆分了两个第三方模块,就要一个个设置过去,有无什么方法能轻便一点呢? 有!

这里运用 node 的 api,fs 模块来读取文件夹里的内容,建立一个 plugins 数组用来寄存大众的插件

const fs = require('fs')

const plugins = [
  // 开辟环境和临盆环境两者均须要的插件
  new HtmlWebpackPlugin({
    title: 'webpack4 实战',
    filename: 'index.html',
    template: path.resolve(__dirname, '..', 'index.html'),
    minify: {
      collapseWhitespace: true
    }
  }),
  new webpack.ProvidePlugin({ $: 'jquery' })
]

const files = fs.readdirSync(path.resolve(__dirname, '../dll'))
console.log(files)

写完可以先输出一下,把 plugins 给解释掉,npm run build 打包看看输出的内容,可以看到文件夹中的内容以数组的情势被打印出来了,今后我们对这个数组做一些轮回操纵就好了

《24 个实例入门并控制「Webpack4」(三)》

完全代码:

const path = require('path')
const fs = require('fs')
const webpack = require('webpack')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin')

// 寄存大众插件
const plugins = [
  // 开辟环境和临盆环境两者均须要的插件
  new HtmlWebpackPlugin({
    title: 'webpack4 实战',
    filename: 'index.html',
    template: path.resolve(__dirname, '..', 'index.html'),
    minify: {
      collapseWhitespace: true
    }
  }),
  new webpack.ProvidePlugin({ $: 'jquery' })
]

// 自动引入 dll 中的文件
const files = fs.readdirSync(path.resolve(__dirname, '../dll'))
files.forEach(file => {
  if (/.*\.dll.js/.test(file)) {
    plugins.push(
      new AddAssetHtmlWebpackPlugin({
        filepath: path.resolve(__dirname, '../dll', file)
      })
    )
  }
  if (/.*\.manifest.json/.test(file)) {
    plugins.push(
      new webpack.DllReferencePlugin({
        manifest: path.resolve(__dirname, '../dll', file)
      })
    )
  }
})

module.exports = {
  entry: {
    app: './src/app.js'
  },
  output: {
    path: path.resolve(__dirname, '..', 'dist')
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: [
          {
            loader: 'babel-loader'
          }
        ]
      },
      {
        test: /\.(png|jpg|jpeg|gif)$/,
        use: [
          {
            loader: 'url-loader',
            options: {
              name: '[name]-[hash:5].min.[ext]',
              limit: 1000, // size <= 1KB
              outputPath: 'images/'
            }
          },
          // img-loader for zip img
          {
            loader: 'image-webpack-loader',
            options: {
              // 紧缩 jpg/jpeg 图片
              mozjpeg: {
                progressive: true,
                quality: 65 // 紧缩率
              },
              // 紧缩 png 图片
              pngquant: {
                quality: '65-90',
                speed: 4
              }
            }
          }
        ]
      },
      {
        test: /\.(eot|ttf|svg)$/,
        use: {
          loader: 'url-loader',
          options: {
            name: '[name]-[hash:5].min.[ext]',
            limit: 5000, // fonts file size <= 5KB, use 'base64'; else, output svg file
            publicPath: 'fonts/',
            outputPath: 'fonts/'
          }
        }
      }
    ]
  },
  plugins,
  performance: false
}

运用 npm run dev 翻开网页也没有问题了,如许自动注入 dll 文件也搞定了,今后还要再打包第三方库只需增添到 webpack.dll.js 内里的 entry 属性中就可以够了

二十一、多页面打包设置

demo21 源码地点

本节运用 demo20 的代码为基本

在 src 目次下新建 list.js 文件,内里写 console.log('这里是 list 页面')

《24 个实例入门并控制「Webpack4」(三)》

在 webpack.base.conf.js 中设置 entry,设置两个进口

module.exports = {
  entry: {
    app: './src/app.js',
    list: './src/list.js'
  }
}

假如如今我们直接 npm run build 打包,在打包自动天生的 index.html 文件中会发明 list.js 也被引入了,申明多进口打包胜利,但并没有完成多个页面的打包,我想打包出 index.htmllist.html 两个页面,而且在 index.html 中引入 app.js,在 list.html 中引入 list.js,该怎么做?

为了轻易演示,先将 webpack.prod.conf.jscacheGroups 新增一个 default 属性,自定义 name

optimization: {
  splitChunks: {
    chunks: 'all',
    cacheGroups: {
      jquery: {
        name: 'jquery', // 零丁将 jquery 拆包
        priority: 15,
        test: /[\\/]node_modules[\\/]jquery[\\/]/
      },
      vendors: {
        test: /[\\/]node_modules[\\/]/,
        name: 'vendors'
      },
      default: {
        name: 'code-segment'
      }
    }
  }
}

翻开 webpack.base.conf.js 文件,将 HtmlWebpackPlugin 拷贝一份,运用 chunks 属性,将须要打包的模块对应写入

// 寄存大众插件
const plugins = [
  new HtmlWebpackPlugin({
    title: 'webpack4 实战',
    filename: 'index.html',
    template: path.resolve(__dirname, '..', 'index.html'),
    chunks: ['app', 'vendors', 'code-segment', 'jquery', 'lodash']
  }),
  new HtmlWebpackPlugin({
    title: '多页面打包',
    filename: 'list.html',
    template: path.resolve(__dirname, '..', 'index.html'),
    chunks: ['list', 'vendors', 'code-segment', 'jquery', 'lodash']
  }),
  new CleanWebpackPlugin(),
  new webpack.ProvidePlugin({ $: 'jquery' })
]

打包后的 dist 目次下天生了两个 html

《24 个实例入门并控制「Webpack4」(三)》

翻开 index.html 可以看到引入的是 app.js,而 list.html 引入的是 list.js,这就是 HtmlWebpackPlugin 插件的 chunks 属性,自定义引入的 js

假如要打包三个页面,再去 copy HtmlWebpackPlugin,经由过程在 entry 中设置,假如有四个,五个,如许手动的复制就比较贫苦了,可以写个要领自动天生 HtmlWebpackPlugin 设置

修正 webpack.base.conf.js

const path = require('path')
const fs = require('fs')
const webpack = require('webpack')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin')
const CleanWebpackPlugin = require('clean-webpack-plugin')

const makePlugins = configs => {
  // 基本插件
  const plugins = [
    new CleanWebpackPlugin(),
    new webpack.ProvidePlugin({ $: 'jquery' })
  ]

  // 依据 entry 自动天生 HtmlWebpackPlugin 设置,设置多页面
  Object.keys(configs.entry).forEach(item => {
    plugins.push(
      new HtmlWebpackPlugin({
        title: '多页面设置',
        template: path.resolve(__dirname, '..', 'index.html'),
        filename: `${item}.html`,
        chunks: [item, 'vendors', 'code-segment', 'jquery', 'lodash']
      })
    )
  })

  // 自动引入 dll 中的文件
  const files = fs.readdirSync(path.resolve(__dirname, '../dll'))
  files.forEach(file => {
    if (/.*\.dll.js/.test(file)) {
      plugins.push(
        new AddAssetHtmlWebpackPlugin({
          filepath: path.resolve(__dirname, '../dll', file)
        })
      )
    }
    if (/.*\.manifest.json/.test(file)) {
      plugins.push(
        new webpack.DllReferencePlugin({
          manifest: path.resolve(__dirname, '../dll', file)
        })
      )
    }
  })

  return plugins
}

const configs = {
  entry: {
    index: './src/app.js',
    list: './src/list.js'
  },
  output: {
    path: path.resolve(__dirname, '..', 'dist')
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: [
          {
            loader: 'babel-loader'
          }
        ]
      },
      {
        test: /\.(png|jpg|jpeg|gif)$/,
        use: [
          {
            loader: 'url-loader',
            options: {
              name: '[name]-[hash:5].min.[ext]',
              limit: 1000, // size <= 1KB
              outputPath: 'images/'
            }
          },
          // img-loader for zip img
          {
            loader: 'image-webpack-loader',
            options: {
              // 紧缩 jpg/jpeg 图片
              mozjpeg: {
                progressive: true,
                quality: 65 // 紧缩率
              },
              // 紧缩 png 图片
              pngquant: {
                quality: '65-90',
                speed: 4
              }
            }
          }
        ]
      },
      {
        test: /\.(eot|ttf|svg)$/,
        use: {
          loader: 'url-loader',
          options: {
            name: '[name]-[hash:5].min.[ext]',
            limit: 5000, // fonts file size <= 5KB, use 'base64'; else, output svg file
            publicPath: 'fonts/',
            outputPath: 'fonts/'
          }
        }
      }
    ]
  },
  performance: false
}

makePlugins(configs)

configs.plugins = makePlugins(configs)

module.exports = configs

再次打包后效果雷同,假如还要增添页面,只需在 entry 中再引入一个 js 文件作为进口即可

多页面设置实在就是定义多个 entry,合营 htmlWebpackPlugin 天生多个 html 页面

二十二、编写 loader

demo22 源码地点

新建文件夹,npm init -ynpm i webpack webpack-cli -D,新建 src/index.js,写入 console.log('hello world')

新建 loaders/replaceLoader.js 文件

module.exports = function(source) {
  return source.replace('world', 'loader')
}

source 参数就是我们的源代码,这里是将源码中的 world 替换成 loader

新建 webpack.config.js

const path = require('path')

module.exports = {
  mode: 'development',
  entry: {
    main: './src/index.js'
  },
  module: {
    rules: [
      {
        test: /.js/,
        use: [path.resolve(__dirname, './loaders/replaceLoader.js')] // 引入自定义 loader
      }
    ]
  },
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].js'
  }
}

目次组织:

《24 个实例入门并控制「Webpack4」(三)》

打包后翻开 dist/main.js 文件,在最底部可以看到 world 已被改成了 loader,一个最简朴的 loader 就写完了

增添 optiions 属性

const path = require('path')

module.exports = {
  mode: 'development',
  entry: {
    main: './src/index.js'
  },
  module: {
    rules: [
      {
        test: /.js/,
        use: [
          {
            loader: path.resolve(__dirname, './loaders/replaceLoader.js'),
            options: {
              name: 'xh'
            }
          }
        ]
      }
    ]
  },
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].js'
  }
}

修正 replaceLoader.js 文件,保留后打包,输出看看效果

module.exports = function(source) {
  console.log(this.query)
  return source.replace('world', this.query.name)
}

《24 个实例入门并控制「Webpack4」(三)》

打包后天生的文件也改成了 options 中定义的 name

更多的设置见官网 API,找到 Loader Interface,内里有个 this.query

《24 个实例入门并控制「Webpack4」(三)》

假如你的 options 不是一个对象,而是按字符串情势写的话,能够会有一些问题,这里官方引荐运用 loader-utils 来猎取 options 中的内容

装置 npm i loader-utils -D,修正 replaceLoader.js

const loaderUtils = require('loader-utils')

module.exports = function(source) {
  const options = loaderUtils.getOptions(this)
  console.log(options)
  return source.replace('world', options.name)
}

console.log(options)console.log(this.query) 输出内容一致

假如你想通报分外的信息出去,return 就不好用了,官网给我们供应了 this.callback API,用法以下

this.callback(
  err: Error | null,
  content: string | Buffer,
  sourceMap?: SourceMap,
  meta?: any
)

修正 replaceLoader.js

const loaderUtils = require('loader-utils')

module.exports = function(source) {
  const options = loaderUtils.getOptions(this)
  const result = source.replace('world', options.name)

  this.callback(null, result)
}

现在没有用到 sourceMap(必需是此模块可剖析的源映照)、meta(可所以任何内容(比方一些元数据)) 这两个可选参数,只将 result 返回归去,保留从新打包后,效果和 return 是一样的

假如在 loader 中写异步代码,会怎样

const loaderUtils = require('loader-utils')

module.exports = function(source) {
  const options = loaderUtils.getOptions(this)

  setTimeout(() => {
    const result = source.replace('world', options.name)
    return result
  }, 1000)
}

《24 个实例入门并控制「Webpack4」(三)》

报错 loader 没有返回,这里运用 this.async 来写异步代码

const loaderUtils = require('loader-utils')

module.exports = function(source) {
  const options = loaderUtils.getOptions(this)

  const callback = this.async()

  setTimeout(() => {
    const result = source.replace('world', options.name)
    callback(null, result)
  }, 1000)
}

模仿一个同步 loader 和一个异步 loader

新建一个 replaceLoaderAsync.js 文件,将之前写的异步代码放入,修正 replaceLoader.js 为同步代码

// replaceLoaderAsync.js

const loaderUtils = require('loader-utils')
module.exports = function(source) {
  const options = loaderUtils.getOptions(this)
  const callback = this.async()
  setTimeout(() => {
    const result = source.replace('world', options.name)
    callback(null, result)
  }, 1000)
}

// replaceLoader.js
module.exports = function(source) {
  return source.replace('xh', 'world')
}

修正 webpack.config.js,loader 的实行递次是从下到上,先实行异步代码,将 world 改成 xh,再实行同步代码,将 xh 改成 world

module: {
  rules: [
    {
      test: /.js/,
      use: [
        {
          loader: path.resolve(__dirname, './loaders/replaceLoader.js')
        },
        {
          loader: path.resolve(__dirname, './loaders/replaceLoaderAsync.js'),
          options: {
            name: 'xh'
          }
        }
      ]
    }
  ]
}

保留后打包,在 mian.js 中可以看到已改成了 hello world,运用多个 loader 也完成了

假如有多个自定义 loader,每次都经由过程 path.resolve(__dirname, xxx) 这类体式格局去写,有无更好的要领?

运用 resolveLoader,定义 modules,当你运用 loader 的时候,会先去 node_modules 中去找,假如没找到就会去 ./loaders 中找

const path = require('path')

module.exports = {
  mode: 'development',
  entry: {
    main: './src/index.js'
  },
  resolveLoader: {
    modules: ['node_modules', './loaders']
  },
  module: {
    rules: [
      {
        test: /.js/,
        use: [
          {
            loader: 'replaceLoader.js'
          },
          {
            loader: 'replaceLoaderAsync.js',
            options: {
              name: 'xh'
            }
          }
        ]
      }
    ]
  },
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].js'
  }
}

二十三、编写 plugin

demo23 源码地点

起首新建一个文件夹,npm 起手式操纵一番,详细的在前几节已说了,不再赘述

在根目次下新建 plugins 文件夹,新建 copyright-webpack-plugin.js,平常我们用的都是 xxx-webpack-plugin,所以我们定名也按如许来,plugin 的定义是一个类

class CopyrightWebpackPlugin {
  constructor() {
    console.log('插件被运用了')
  }
  apply(compiler) {}
}

module.exports = CopyrightWebpackPlugin

在 webpack.config.js 中运用,所以每次运用 plugin 都要运用 new,因为本质上 plugin 是一个类

const path = require('path')
const CopyrightWebpackPlugin = require('./plugins/copyright-webpack-plugin')

module.exports = {
  mode: 'development',
  entry: {
    main: './src/index.js'
  },
  plugins: [new CopyrightWebpackPlugin()],
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].js'
  }
}

保留后打包,插件被运用了,只不过我们什么都没干

《24 个实例入门并控制「Webpack4」(三)》

假如我们要通报参数,可以如许

new CopyrightWebpackPlugin({
  name: 'xh'
})

同时在 copyright-webpack-plugin.js 中吸收

class CopyrightWebpackPlugin {
  constructor(options) {
    console.log('插件被运用了')
    console.log('options = ', options)
  }
  apply(compiler) {}
}

module.exports = CopyrightWebpackPlugin

《24 个实例入门并控制「Webpack4」(三)》

我们先把 constructor 解释掉,期近将要把打包的效果,放入 dist 目次之前的这个时候,我们来做一些操纵

apply(compiler) {} compiler 可以看做是 webpack 的实例,详细见官网 compiler-hooks

hooks 是钩子,像 vue、react 的生命周期一样,找到 emit 这个时候,将打包效果放入 dist 目次前实行,这里是个 AsyncSeriesHook 异步要领

《24 个实例入门并控制「Webpack4」(三)》

class CopyrightWebpackPlugin {
  apply(compiler) {
    compiler.hooks.emit.tapAsync(
      'CopyrightWebpackPlugin',
      (compilation, cb) => {
        console.log(11)
        cb()
      }
    )
  }
}

module.exports = CopyrightWebpackPlugin

因为 emit异步的,可以经由过程 tapAsync 来写,当要把代码放入到 dist 目次之前,就会触发这个钩子,走到我们定义的函数里,假如你用 tapAsync 函数,记得末了要用 cb() ,tapAsync 要通报两个参数,第一个参数通报我们定义的插件称号

保留后再次打包,我们写的内容也输出了

《24 个实例入门并控制「Webpack4」(三)》

compilation 这个参数里寄存了此次打包的一切内容,可以输出一下 compilation.assets 看一下

《24 个实例入门并控制「Webpack4」(三)》

返回效果是一个对象,main.js 是 key,也就是打包后天生的文件名及文件后缀,我们可以来模仿一下

class CopyrightWebpackPlugin {
  apply(compiler) {
    compiler.hooks.emit.tapAsync(
      'CopyrightWebpackPlugin',
      (compilation, cb) => {
        // 天生一个 copyright.txt 文件
        compilation.assets['copyright.txt'] = {
          source: function() {
            return 'copyright by xh'
          },
          size: function() {
            return 15 // 上面 source 返回的字符长度
          }
        }
        console.log('compilation.assets = ', compilation.assets)
        cb()
      }
    )
  }
}

module.exports = CopyrightWebpackPlugin

《24 个实例入门并控制「Webpack4」(三)》

在 dist 目次下天生了 copyright.txt 文件

之前引见的是异步钩子,如今运用同步钩子

《24 个实例入门并控制「Webpack4」(三)》

class CopyrightWebpackPlugin {
  apply(compiler) {
    // 同步钩子
    compiler.hooks.compile.tap('CopyrightWebpackPlugin', compilation => {
      console.log('compile')
    })

    // 异步钩子
    compiler.hooks.emit.tapAsync(
      'CopyrightWebpackPlugin',
      (compilation, cb) => {
        compilation.assets['copyright.txt'] = {
          source: function() {
            return 'copyright by xh'
          },
          size: function() {
            return 15 // 字符长度
          }
        }
        console.log('compilation.assets = ', compilation.assets)
        cb()
      }
    )
  }
}

module.exports = CopyrightWebpackPlugin

二十四、编写 Bundle

demo24 源码地点

模块剖析

在 src 目次下新建三个文件 word.jsmessage.jsindex.js,对应的代码:

// word.js
export const word = 'hello'

// message.js
import { word } from './word.js'

const message = `say ${word}`

export default message

// index.js
import message from './message.js'

console.log(message)

新建 bundle.js

const fs = require('fs')

const moduleAnalyser = filename => {
  const content = fs.readFileSync(filename, 'utf-8')
  console.log(content)
}

moduleAnalyser('./src/index.js')

运用 node 的 fs 模块,读取文件信息,并在掌握台输出,这里全局装置一个插件,来显现代码高亮,npm i cli-highlight -g,运转 node bundle.js | highlight

《24 个实例入门并控制「Webpack4」(三)》

index.js 中的代码已被输出到掌握台上,而且代码有高亮,轻易浏览,读取进口文件信息就完成了

如今我们要读取 index.js 文件中运用的 message.js 依靠,import message from './message.js'

装置一个第三方插件 npm i @babel/parser

@babel/parser 是 Babel 中运用的 JavaScript 剖析器。

官网也供应了响应的示例代码,依据示例代码来模仿,修正我们的文件

《24 个实例入门并控制「Webpack4」(三)》

const fs = require('fs')
const parser = require('@babel/parser')

const moduleAnalyser = filename => {
  const content = fs.readFileSync(filename, 'utf-8')
  console.log(
    parser.parse(content, {
      sourceType: 'module'
    })
  )
}

moduleAnalyser('./src/index.js')

我们运用的是 es6 的 module 语法,所以 sourceType: 'module'

《24 个实例入门并控制「Webpack4」(三)》

保留后运转,输出了 AST (笼统语法树),内里有一个 body 字段,我们输出这个字段

const fs = require('fs')
const parser = require('@babel/parser')

const moduleAnalyser = filename => {
  const content = fs.readFileSync(filename, 'utf-8')
  const ast = parser.parse(content, {
    sourceType: 'module'
  })
  console.log(ast.program.body)
}

moduleAnalyser('./src/index.js')

打印出了两个 Node 节点,第一个节点的 type 是 ImportDeclaration(引入的声明),对比我们在 index.js 中写的 import message from './message.js',第二个节点的 type 是 ExpressionStatement (表达式的声明),对比我们写的 console.log(message)

《24 个实例入门并控制「Webpack4」(三)》

运用 babel 来帮我们天生笼统语法树,我们再导入 import message1 from './message1.js' 再运转

《24 个实例入门并控制「Webpack4」(三)》

笼统语法树将我们的 js 代码转成了对象的情势,如今就可以够遍历笼统语法树天生的节点对象中的 type,是不是为 ImportDeclaration,就可以找到代码中引入的依靠了

再借助一个东西 npm i @babel/traverse

const fs = require('fs')
const parser = require('@babel/parser')
const traverse = require('@babel/traverse').default

const moduleAnalyser = filename => {
  const content = fs.readFileSync(filename, 'utf-8')
  const ast = parser.parse(content, {
    sourceType: 'module'
  })
  traverse(ast, {
    ImportDeclaration({ node }) {
      console.log(node)
    }
  })
}

moduleAnalyser('./src/index.js')

《24 个实例入门并控制「Webpack4」(三)》

只打印了两个 ImportDeclaration,遍历完毕,我们只须要取到依靠的文件名,在打印的内容中,每一个节点都有个 source 属性,内里有个 value 字段,示意的就是文件途径及文件名

const fs = require('fs')
const parser = require('@babel/parser')
const traverse = require('@babel/traverse').default

const moduleAnalyser = filename => {
  const content = fs.readFileSync(filename, 'utf-8')
  const ast = parser.parse(content, {
    sourceType: 'module'
  })
  const dependencise = []
  traverse(ast, {
    ImportDeclaration({ node }) {
      dependencise.push(node.source.value)
    }
  })
  console.log(dependencise)
}

moduleAnalyser('./src/index.js')

保留完从新运转,输出效果:

['./message.js', './message1.js']

如许就对进口文件的依靠剖析就剖析出来了,如今把 index.js 中引入的 message1.js 的依靠给删除,这里有个注重点,打印出来的文件途径是相对途径,相对于 src/index.js 文件,然则我们打包的时候不能是进口文件(index.js)的相对途径,而应当是根目次的相对途径(或许说是绝对途径),借助 node 的 api,引入一个 path

const fs = require('fs')
const path = require('path')
const parser = require('@babel/parser')
const traverse = require('@babel/traverse').default

const moduleAnalyser = filename => {
  const content = fs.readFileSync(filename, 'utf-8')
  const ast = parser.parse(content, {
    sourceType: 'module'
  })
  const dependencise = []
  traverse(ast, {
    ImportDeclaration({ node }) {
      const dirname = path.dirname(filename)
      console.log(dirname)
      dependencise.push(node.source.value)
    }
  })
  // console.log(dependencise)
}

moduleAnalyser('./src/index.js')

输出为 ./src,继承修正

ImportDeclaration({ node }) {
  const dirname = path.dirname(filename)
  const newFile = path.join(dirname, node.source.value)
  console.log(newFile)
  dependencise.push(node.source.value)
}

输出为 src\message.js

windows 和 类 Unix(linux/mac),途径是有区分的。windows 是用反斜杠 支解目次或许文件的,而在类 Unix 的体系中是用的
/

因为我是 windows 体系,所以这里输出为 src\message.js,而类 Unix 输出的为 src/message.js

.\src\message.js 这个途径是我们真正打包时要用到的途径

newFile .\src\message.js
[ '.\\src\\message.js' ]

既存一个相对途径,又存一个绝对途径

const fs = require('fs')
const path = require('path')
const parser = require('@babel/parser')
const traverse = require('@babel/traverse').default

const moduleAnalyser = filename => {
  const content = fs.readFileSync(filename, 'utf-8')
  const ast = parser.parse(content, {
    sourceType: 'module'
  })
  const dependencise = {}
  traverse(ast, {
    ImportDeclaration({ node }) {
      const dirname = path.dirname(filename)
      const newFile = '.\\' + path.join(dirname, node.source.value)
      console.log('newFile', newFile)
      dependencise[node.source.value] = newFile
    }
  })
  console.log(dependencise)
  return {
    filename,
    dependencise
  }
}

moduleAnalyser('./src/index.js')
newFile .\src\message.js
{ './message.js': '.\\src\\message.js' }

因为我们写的代码是 es6,浏览器没法辨认,照样须要 babel 来做转换

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

'use strict'

var _message = _interopRequireDefault(require('./message.js'))

function _interopRequireDefault(obj) {
  return obj && obj.__esModule ? obj : { default: obj }
}

console.log(_message.default)
const fs = require('fs')
const path = require('path')
const parser = require('@babel/parser')
const traverse = require('@babel/traverse').default
const babel = require('@babel/core')

const moduleAnalyser = filename => {
  const content = fs.readFileSync(filename, 'utf-8')
  const ast = parser.parse(content, {
    sourceType: 'module'
  })
  const dependencise = {}
  traverse(ast, {
    ImportDeclaration({ node }) {
      const dirname = path.dirname(filename)
      const newFile = '.\\' + path.join(dirname, node.source.value)
      dependencise[node.source.value] = newFile
    }
  })
  const { code } = babel.transformFromAst(ast, null, {
    presets: ['@babel/preset-env']
  })
  return {
    filename,
    dependencise,
    code
  }
}

const moduleInfo = moduleAnalyser('./src/index.js')
console.log(moduleInfo)

剖析的效果就在掌握台上打印了

{ filename: './src/index.js',
  dependencise: { './message.js': '.\\src\\message.js' },
  code:
   '"use strict";\n\nvar _message = _interopRequireDefault(require("./message.js"));\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }\n\nconsole.log(_message.default);' }

现在我们只对一个模块举行剖析,接下来要对全部项目举行剖析,所以我们先剖析了进口文件,再剖析进口文件中所运用的依靠

依靠图谱

建立一个函数来轮回依靠并天生图谱

// 依靠图谱
const makeDependenciesGraph = entry => {
  const entryModule = moduleAnalyser(entry)
  const graphArray = [entryModule]
  for (let i = 0; i < graphArray.length; i++) {
    const item = graphArray[i]
    const { dependencise } = item
    // 假如进口文件有依靠就去做轮回依靠,对每一个依靠做剖析
    if (dependencise) {
      for (const j in dependencise) {
        if (dependencise.hasOwnProperty(j)) {
          graphArray.push(moduleAnalyser(dependencise[j]))
        }
      }
    }
  }
  console.log('graphArray = ', graphArray)
}

将进口的依靠,依靠中的依靠悉数都剖析完放到 graphArray 中,掌握台输出的打印效果

《24 个实例入门并控制「Webpack4」(三)》

可以看到 graphArray 中一共有三个对象,就是我们在项目中引入的三个文件,悉数被剖析出来了,为了轻易浏览,我们建立一个 graph 对象,将剖析的效果顺次放入

// 依靠图谱
const makeDependenciesGraph = entry => {
  const entryModule = moduleAnalyser(entry)
  const graphArray = [entryModule]
  for (let i = 0; i < graphArray.length; i++) {
    const item = graphArray[i]
    const { dependencise } = item
    // 假如进口文件有依靠就去做轮回依靠,对每一个依靠做剖析
    if (dependencise) {
      for (const j in dependencise) {
        if (dependencise.hasOwnProperty(j)) {
          graphArray.push(moduleAnalyser(dependencise[j]))
        }
      }
    }
  }
  // console.log('graphArray = ', graphArray)

  // 建立一个对象,将剖析后的效果放入
  const graph = {}
  graphArray.forEach(item => {
    graph[item.filename] = {
      dependencise: item.dependencise,
      code: item.code
    }
  })
  console.log('graph = ', graph)
  return graph
}

输出的 graph 为:

《24 个实例入门并控制「Webpack4」(三)》

末了在 makeDependenciesGraph 函数中将 graph 返回,赋值给 graphInfo,输出的效果和 graph 是一样的

const graghInfo = makeDependenciesGraph('./src/index.js')
console.log(graghInfo)

天生代码

如今已拿到了一切代码天生的效果,如今我们借助 DependenciesGraph(依靠图谱) 来天生真正能在浏览器上运转的代码

最好放在一个大的闭包中来实行,防止污染全局环境

const generateCode = entry => {
  // makeDependenciesGraph 返回的是一个对象,须要转换成字符串
  const graph = JSON.stringify(makeDependenciesGraph(entry))
  return `
    (function (graph) {

    })(${graph})
  `
}

const code = generateCode('./src/index.js')
console.log(code)

《24 个实例入门并控制「Webpack4」(三)》

我这里先把输出的 graph 代码花样化了一下,可以发如今 index.js 用到了 require 要领,message.js 中不仅用了 require 要领,还用 exports 对象,然则在浏览器中,这些都是不存在的,假如我们直接去实行,是会报错的

let graph = {
  './src/index.js': {
    dependencise: { './message.js': '.\\src\\message.js' },
    code: `
      "use strict";\n\n
       var _message = _interopRequireDefault(require("./message.js"));\n\n
       function _interopRequireDefault(obj){ return obj && obj.__esModule ? obj : { default: obj }; } \n\n
       console.log(_message.default);
      `
  },
  '.\\src\\message.js': {
    dependencise: { './word.js': '.\\src\\word.js' },
    code:
      '"use strict";\n\nObject.defineProperty(exports, "__esModule", {\n  value: true\n});\nexports.default = void 0;\n\nvar _word = require("./word.js");\n\nvar message = "say ".concat(_word.word);\nvar _default = message;\nexports.default = _default;'
  },
  '.\\src\\word.js': {
    dependencise: {},
    code:
      '"use strict";\n\nObject.defineProperty(exports, "__esModule", {\n  value: true\n});\nexports.word = void 0;\nvar word = \'hello\';\nexports.word = word;'
  }
}

接下来要去组织 require 要领和 exports 对象

const generateCode = entry => {
  console.log(makeDependenciesGraph(entry))
  // makeDependenciesGraph 返回的是一个对象,须要转换成字符串
  const graph = JSON.stringify(makeDependenciesGraph(entry))
  return `
    (function (graph) {
      // 定义 require 要领
      function require(module) {

      };
      require('${entry}')
    })(${graph})
  `
}

const code = generateCode('./src/index.js')
console.log(code)

graph 是依靠图谱,拿到 entry 后去实行 ./src/index.js 中的 code,也就是下面高亮部份的代码,为了直观我把前面输出的 graph 代码拿下来参考:

let graph = {
  './src/index.js': {
    dependencise: { './message.js': '.\\src\\message.js' },
    code: `
      "use strict";\n\n
       var _message = _interopRequireDefault(require("./message.js"));\n\n
       function _interopRequireDefault(obj){ return obj && obj.__esModule ? obj : { default: obj }; } \n\n
       console.log(_message.default);
      `
  }
}

为了让 code 中的代码实行,这里再运用一个闭包,让每一个模块里的代码放到闭包里来实行,如许模块的变量就不会影响到外部的变量

return `
    (function (graph) {
      // 定义 require 要领
      function require(module) {
        (function (code) {
          eval(code)
        })(graph[module].code)
      };
      require('${entry}')
    })(${graph})
  `

闭包里通报的是 graph[module].code,如今 entry 也就是 ./src/index.js 这个文件,会传给 require 中的 module 变量,现实上去找依靠图谱中 ./src/index.js 对应的对象,然后再去找到 code 中对应的代码,也就是下面这段代码,被我花样化过,为了演示效果

'use strict'
var _message = _interopRequireDefault(require('./message.js'))
function _interopRequireDefault(obj) {
  return obj && obj.__esModule ? obj : { default: obj }
}
console.log(_message.default)

然则我们会发明,这里 _interopRequireDefault(require('./message.js')) 引入的是 ./message.js 相对途径,比及第二次实行的时候,require(module) 这里的 module 对应的就是 ./message.js

它会到 graph 中去找 ./message.js 下对应的 code,但是我们在 graph 中存的是 '.\\src\\message.js' 绝对途径,如许就会找不到对象

因为我们之前写代码的时候引入的是相对途径,如今我们要把相对途径转换成绝对途径才准确实行,定义一个 localRequire 要领,如许当下次去找的时候就会走我们本身定义的 localRequire,实在就是一个相对途径转换的要领

return `
    (function (graph) {
      // 定义 require 要领
      function require(module) {
        // 相对途径转换
        function localRequire(relativePath) {
          return require(graph[module].dependencise[relativePath])
        }
        (function (require, code) {
          eval(code)
        })(localRequire, graph[module].code)
      };
      require('${entry}')
    })(${graph})
  `

我们定义了 localRequire 要领,并把它通报到闭包里,当实行了 eval(code) 时实行了 require 要领,就不是实行外部的 require(module) 这个要领,而是实行我们通报进去的 localRequire 要领

我们在剖析出的代码中是如许引入 message.js

var _message = _interopRequireDefault(require('./message.js'))

这里挪用了 require('./message.js'),就是我们上面写的 require 要领,也就是 localRequire(relativePath)

所以 relativePath 就是 './message.js'

这个要领返回的是 require(graph[module].dependencise[relativePath])

这里我把参数带进去,就是如许:

graph('./src/index.js').dependencise['./message.js']

let graph = {
  './src/index.js': {
    dependencise: { './message.js': '.\\src\\message.js' },
    code: `
      "use strict";\n\n
       var _message = _interopRequireDefault(require("./message.js"));\n\n
       function _interopRequireDefault(obj){ return obj && obj.__esModule ? obj : { default: obj }; } \n\n
       console.log(_message.default);
      `
  }
}

对比着图谱就可以发明终究返回的就是 '.\\src\\message.js' 绝对途径,返回绝对途径后,我们再挪用 require(graph('./src/index.js').dependencise['./message.js']) 就是实行外部定义的 require(module) 这个要领,从新递归的去实行,光如许还不够,这只是完成了 require 要领,还差 exports 对象,所以我们再定义一个 exports 对象

return `
    (function (graph) {
      // 定义 require 要领
      function require(module) {
        // 相对途径转换
        function localRequire(relativePath) {
          return require(graph[module].dependencise[relativePath])
        }
        var exports = {};
        (function (require, exports, code) {
          eval(code)
        })(localRequire, exports, graph[module].code)
        return exports
      };
      require('${entry}')
    })(${graph})
  `

末了要记得 return exports 将 exports 导出,如许下一个模块在引入这个模块的时候才拿到导出的效果,如今代码天生的流程就写完了,终究返回的是一个大的字符串,保留再次运转 node bundle.js | highlight

《24 个实例入门并控制「Webpack4」(三)》

这里我是 windows 环境,将输出完的代码直接放到浏览器里不可,我就把紧缩的代码花样化成下面这类模样,再放到浏览器里就可以输出胜利了

;(function(graph) {
  function require(module) {
    function localRequire(relativePath) {
      return require(graph[module].dependencise[relativePath])
    }
    var exports = {}
    ;(function(require, exports, code) {
      eval(code)
    })(localRequire, exports, graph[module].code)
    return exports
  }
  require('./src/index.js')
})({
  './src/index.js': {
    dependencise: { './message.js': '.\\src\\message.js' },
    code:
      '"use strict";\n\nvar _message = _interopRequireDefault(require("./message.js"));\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }\n\nconsole.log(_message.default);'
  },
  '.\\src\\message.js': {
    dependencise: { './word.js': '.\\src\\word.js' },
    code:
      '"use strict";\n\nObject.defineProperty(exports, "__esModule", {\n  value: true\n});\nexports.default = void 0;\n\nvar _word = require("./word.js");\n\nvar message = "say ".concat(_word.word);\nvar _default = message;\nexports.default = _default;'
  },
  '.\\src\\word.js': {
    dependencise: {},
    code:
      '"use strict";\n\nObject.defineProperty(exports, "__esModule", {\n  value: true\n});\nexports.word = void 0;\nvar word = \'hello\';\nexports.word = word;'
  }
})

将上面代码放入浏览器的掌握台中,回车就可以输出 say hello

《24 个实例入门并控制「Webpack4」(三)》

总结

这就是打包东西打包后的内容,时期触及了 node 学问,运用 babel 来转译 ast(笼统语法树),末了的 generateCode 函数触及到了递归闭包形参实参,须要人人多看几遍,加深明白

To Be Continued

个人博客

24 个实例入门并掌握「Webpack4」(一)

24 个实例入门并掌握「Webpack4」(二)

    原文作者:ITxiaohao
    原文地址: https://segmentfault.com/a/1190000019132719
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞