Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Webpack #115

Open
xiaotiandada opened this issue Jun 25, 2022 · 0 comments
Open

Webpack #115

xiaotiandada opened this issue Jun 25, 2022 · 0 comments

Comments

@xiaotiandada
Copy link
Owner

xiaotiandada commented Jun 25, 2022

Babel 7 不需要 ts-loader。从 Babel 7 开始,ts-loader 是不必要的,因为 Babel 7 支持 TypeScript

Notes

webpack4

version: 4.31.0

01丨课程介绍

image-20221025120059984

02丨内容综述

image-20221025120209481

image-20221025120227550

03丨为什么需要构建工具

image-20221025120459282

04丨前端构建演变之路

image-20221025120539534

05丨为什么选择webpack

image-20221025120645148

image-20221025120703101

06丨初识webpack

image-20221026004230452

image-20221026004300308

image-20221026004329388

只设定了 entry output

07丨环境搭建:安装webpack

  • NVM
  • Node.js
  • NPM
yarn init
yarn add webpack webpack-cli -D

./node_modules/.bin/webpack -v
webpack: 5.74.0
webpack-cli: 4.10.0
webpack-dev-server not installed

08丨webpack初体验:一个最简单的例子

'use strict'

const path = require('path')

module.exports = {
  entry: './src/index.js',
  output: {
    path: path.join(__dirname, 'dist'),
    filename: 'bundle.js',
  },
  mode: 'production',
}
import { helloworld } from './helloworld'

document.write(helloworld())
export function helloworld() {
  return 'Hello webpack'
}

09丨通过npm script运行webpack

image-20221026005943014

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

10丨webpack核心概念之entry用法

Entry 用来指定 webpack 的打包入口

image-20221026010215258

image-20221026010255896

11丨webpack核心概念之output

Output 用来告诉 webpack 如何将编译后的文件输出到磁盘

image-20221026010419722

image-20221026010438954

12丨webpack核心概念之loaders

image-20221027011615046

image-20221027014443748

image-20221027014513796

13丨webpack核心概念之plugins

image-20221027014824132

image-20221027014901916

image-20221027014933799

14丨webpack核心概念之mode

image-20221027015011643

image-20221027015055654

15丨解析ES6和React JSX

import React from 'react'
import ReactDOM from 'react-dom'

class Hello extends React.Component {
  render() {
    return <div>Hello {this.props.toWhat}</div>
  }
}

const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(<Hello toWhat="World" />)
'use strict'

const path = require('path')

module.exports = {
  entry: {
    index: './src/index.js',
    search: './src/search.js',
  },
  output: {
    path: path.join(__dirname, 'dist'),
    filename: '[name].js',
  },
  mode: 'production',
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
        },
      },
    ],
  },
}
{
  "presets": ["@babel/preset-env", "@babel/preset-react"],
  "plugins": ["@babel/plugin-proposal-class-properties"]
}
  "devDependencies": {
    "@babel/core": "^7.19.6",
    "@babel/plugin-proposal-class-properties": "^7.18.6",
    "@babel/preset-env": "^7.19.4",
    "@babel/preset-react": "^7.18.6",
    "babel-loader": "^8.2.5",
    "webpack": "^5.74.0",
    "webpack-cli": "^4.10.0"
  },
  "dependencies": {
    "react": "^18.2.0",
    "react-dom": "^18.2.0"
  }
}

16丨解析CSS、Less和Sass

image-20221027115129916

链式调用 从右到左

image-20221027115336207

'use strict'

const path = require('path')

module.exports = {
  entry: {
    index: './src/index.js',
    search: './src/search.js',
  },
  output: {
    path: path.join(__dirname, 'dist'),
    filename: '[name].js',
  },
  mode: 'production',
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
        },
      },
      {
        test: /\.css$/i,
        use: ['style-loader', 'css-loader'],
      },
      {
        test: /\.less$/i,
        use: [
          // compiles Less to CSS
          'style-loader',
          'css-loader',
          'less-loader',
        ],
      },
    ],
  },
}

17丨解析图片和字体

import img from './file.png';

module.exports = {
  module: {
    rules: [
      {
        test: /\.(png|jpe?g|gif)$/i,
        use: [
          {
            loader: 'file-loader',
          },
        ],
      },
      {
        test: /\.(woff|woff2|eot|ttf|otf)$/i,
        use: [
          {
            loader: 'file-loader',
          },
        ],
      },
    ],
  },
};

file-loader 导入了,换了好几种字体都没成功...

@font-face {
  font-family: 'SourceHanSerifSC-Heavy';
  src: url('./fonts/SourceHanSerifSC-Heavy.otf') format('tryetype');
}

body {
  font-family: 'SourceHanSerifSC-Heavy';
}
module.exports = {
  module: {
    rules: [
      {
        test: /\.(png|jpg|gif)$/i,
        use: [
          {
            loader: 'url-loader',
            options: {
              limit: true,
            },
          },
        ],
      },
    ],
  },
};
<img src="data:image/jpeg;base64....." />

18丨webpack中的文件监听

image-20221027143026278

image-20221027143102316

image-20221027143137587

19丨webpack中的热更新及原理分析

image-20221027144403799

image-20221027144530060

image-20221027144652357

20丨文件指纹策略:chunkhash、contenthash和hash

image-20221027150951865

image-20221027151036706

image-20221027151427395

JS 的文件指纹设置

设置 output 的 filename,使用[chunnkhash] [name][chunkhash:8].js

CSS 的文件指纹设置

设置 MiniCssExtractPlugin 的 filename, 使用 [contenthash] [name][commtenthash:8].css

图片的文件指纹设置

[name][hash:8].[ext]

21丨HTML 、CSS和JS代码压缩

HTML CSS JS 压缩

JS

内置了

CSS

HTML

22丨自动清理构建目录产物

每次构建的时候不会清理目录,造成构建的输出目录 ouput 文件越来越多

rm -rf ./dist && webpack
rimraf ./dist && webpack

23丨PostCSS插件autoprefixer自动补齐CSS3前缀

// package.json
"browserslist": {
    "production": [
      "last 2 version",
      ">0.2%"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version",
      "last 1 ie version"
    ]
  }
// postcss.config.js
module.exports = {
  plugins: [require('autoprefixer')],
}

24丨移动端CSS px自动转换成rem

image-20221028114409511

image-20221028114441932

image-20221028114511058

25丨静态资源内联

image-20221028115300273

image-20221028115346229

image-20221028115418689

26丨多页面应用打包通用方案

多页面应用(MPA)概念

每一次页面跳转的时候,后台服务器都会给返回一个新的 html 文档,这种类型的网站也就是多页网站,也叫做多页应用。

多页面打包基本思路

每个页面对应一个 entry,一个 html- webpack- plugin

缺点:每次新增或删除页面需要改 webpack 配置

多页面打包通用方案

动态获取 entry 和设置 html- webpack- plugin 数量 利用 glob.sync

entry: glob.sync (path.join (_dirname, './src/*/index.js'),

'use strict'

const glob = require('glob')
const path = require('path')
// const webpack = require('webpack')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')

const setMPA = () => {
  const entry = {}
  const htmlWebpackPlugins = []

  //  '/Users/x/Code/Case/webpack/learn2/src/index/index.js',
  //  '/Users/x/Code/Case/webpack/learn2/src/search/index.js'
  const entryFiles = glob.sync(path.join(__dirname, './src/*/index.js'))

  Object.keys(entryFiles).map((index) => {
    const entryFile = entryFiles[index]

    const match = entryFile.match('/src/(.*)/index.js')
    const pageName = match && match[1]

    entry[pageName] = entryFile

    htmlWebpackPlugins.push(
      new HtmlWebpackPlugin({
        template: path.join(__dirname, `src/${pageName}/index.html`),
        filename: `${pageName}.html`,
        chunks: [pageName],
      })
    )
  })

  return {
    entry,
    htmlWebpackPlugins,
  }
}

const { entry, htmlWebpackPlugins } = setMPA()

module.exports = {
  entry: entry,
  output: {
    path: path.join(__dirname, 'dist'),
    filename: '[name]_[chunkhash:8].js',
  },
  mode: 'production',
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
        },
      },
      {
        test: /\.css$/i,
        use: [MiniCssExtractPlugin.loader, 'css-loader'],
      },
      {
        test: /\.less$/i,
        sideEffects: true,
        use: [
          // compiles Less to CSS
          MiniCssExtractPlugin.loader,
          'css-loader',
          'less-loader',
          {
            loader: 'postcss-loader',
          },
        ],
      },
      {
        test: /\.(png|jpe?g|gif)$/i,
        use: [
          {
            loader: 'file-loader',
            options: {
              name: '[name]_[hash:8].[ext]',
            },
          },
        ],
      },
      {
        test: /\.(woff|woff2|eot|ttf|otf)$/i,
        use: [
          {
            loader: 'file-loader',
            options: {
              name: '[name]_[hash:8].[ext]',
            },
          },
        ],
      },
    ],
  },
  optimization: {
    minimizer: [new CssMinimizerPlugin()],
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: '[name]_[contenthash:8].css',
    }),
    // new webpack.HotModuleReplacementPlugin()
    new CleanWebpackPlugin(),
  ].concat(htmlWebpackPlugins),
  devServer: {
    static: {
      directory: path.join(__dirname, 'dist'),
    },
    compress: true,
    hot: true,
  },
}

27丨使用sourcemap

作用:通过 source map 定位到源代码

开发环境开启,线上环境关闭

  • 线上排查问题的时候可以将 sourcemap 上传到错误监控系统

Source map 关键字

  • eval:使用 eval 包裹模块代码

  • source map:产生 .Map 文件

  • cheap:不包含列信息

  • inline:将.Map 作为 DataURI 嵌入,不单独生成.Map 文件

  • module:包含 loader 的 sourcemap

image-20221028151453409

default

image-20221028153252920

devtool: 'source-map'

image-20221028152920605

devtool: 'cheap-source-map',

image-20221028153142936

28丨提取页面公共资源

基础库分离

  • 思路:将 react、react-dom 基础包通过 cdn 引入,不打入 bundle 中

  • 方法:使用 html- webpack- externals-plugin

利用 SplitChunksPlugin 进行公共脚本分离

Webpack4 内置的,替代 CommonsChunkPlugin:插件 chunks 参数说明:

  • async 异步引入的库进行分离(默认)

  • initial 同步引入的库进行分离

  • all 所有引入的库进行分离(推荐)

利用 SplitChunksPlugin 分离基础包

test:匹配出需要分离的包

利用 SplitChunksPlugin 分离页面公共文件

minChunks:设置最小引用次数为 2 次

minuSize:分离的包体积的大小 name: 'commons',

{
  plugins: [
       new HtmlWebpackPlugin({
        template: path.join(__dirname, `src/${pageName}/index.html`),
        filename: `${pageName}.html`,
        // chunks 不配置 'vendors', 'commons' 也讷讷个工作
        chunks: ['vendors', 'commons', pageName], 
      })
  ],
  optimization: {
    // minSize: 0 不生效,在 common 里面导入了 lodash 体积变大才功能打包出来
    minimizer: [new CssMinimizerPlugin()],
    splitChunks: {
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/,
          name: 'vendors',
          chunks: 'all',
        },
        commons: {
          name: 'commons',
          chunks: 'all',
          minChunks: 2,
        },
      },
    },
  },
}
assets by info 753 KiB [immutable]
  assets by path *.js 689 KiB
    asset commons_b8135d34.js 528 KiB [emitted] [immutable] [big] (name: commons) (id hint: commons)
    asset vendors_dcd4109b.js 137 KiB [emitted] [immutable] (name: vendors) (id hint: vendor)
    asset search_b218e1ce.js 17.2 KiB [emitted] [immutable] (name: search)
    asset index_e3aea713.js 7.62 KiB [emitted] [immutable] (name: index)
  asset img_f48f97d1.jpeg 64 KiB [emitted] [immutable] [from: src/images/img.jpeg] (auxiliary name: search)
  asset search_6e9cebe1.css 123 bytes [emitted] [immutable] [minimized] (name: search)
assets by path *.html 844 bytes
  asset search.html 476 bytes [emitted]
  asset index.html 368 bytes [emitted]
Entrypoint index [big] 535 KiB = commons_b8135d34.js 528 KiB index_e3aea713.js 7.62 KiB
Entrypoint search [big] 682 KiB (64 KiB) = commons_b8135d34.js 528 KiB vendors_dcd4109b.js 137 KiB search_b218e1ce.js 17.2 KiB search_6e9cebe1.css 123 bytes 1 auxiliary asset

当 webpack 处理文件路径时,它们总是包含/在 Unix 系统和\Windows 上。这就是为什么必须使用[\\/]in{cacheGroup}.test字段来表示路径分隔符。/\{cacheGroup}.test跨平台使用时会导致问题。

29丨treeshaking的使用和原理分析

Tree shaking(摇树优化)

概念:1 个模块可能有多个方法,只要其中的某个方法使用到了,则整个文件都会被打到 bundle 里面去,tree shaking 就是只把用到的方法打入 bundle,没用到的方法会在 uglify 阶段被擦除掉。

使用:webpack 默认支持,在。Babelrc 里设置 modules: false 即可

· production model 的情况下默认开启

要求:必须是 ES6 的语法,CJS 的方式不支持

摇树,顾名思义,就是将枯黄的落叶摇下来,只留下树上活的叶子。枯黄的落叶代表项目中未引用的无用代码,活的树叶代表项目中实际用到的源码。https://juejin.cn/post/6996816316875161637#heading-40

DCE (Elimination)

代码不会被执行,不可到达代码执行的结果不会被用到代码只会影响死变量(只写不读)

if (false) {
	console.log('这段代码永远不会执行')
}

Tree- shaking 原理

利用 ES6 模块的特点:

  • 只能作为模块顶层的语句出现

  • import 的模块名只能是字符串常量

  • import binding 是 immutablel 的

代码擦除:uglify 阶段删除无用代码

30丨ScopeHoisting使用和原理分析

image-20221031232538654

会导致什么问题?

大量函数闭包包裹代码,导致体积增大(模块越多越明显)

运行代码时创建的函数作用域变多,内存开销变大

image-20221031232641769

结论:

  • 被 webpack 转换后的模块会带上一层包裹
  • import 会被转换成_ webpack_ require

进一步分析 webpack 的模块机制

image-20221031232951481

分析:

  • 打包出来的是一个 IFE(匿名闭包)

  • modules 是一个数组,每一项是一个模块初始化函数

  • _ webpack. Require 用来加载模块,返回 module. Exports

  • 通过 WEBPACK_ REQUIRE_ METHOD (O)启动程序

Scope hoisting 原理

原理:将所有模块的代码按照引用顺序放在一个函数作用域里,然后适当的重命名一些变量以防止变量名冲突

对比:通过 scope hoisting 可以减少函数声明代码和内存开销

image-20221031233341816

Scope hoisting 使用

webpack mode 为 production 默认开启必须是 ES6 语法,CJS 不支持

// 历史版本 3 可能需要手动配置
new webpack.optimize.ModuleConcatenationPlugin();

31丨代码分割和动态import

代码分割的意义

对于大的 Web 应用来讲,将所有的代码都放在一个文件中显然是不够有效的,特别是当你的某些代码块是在某些特殊的时候才会被使用到。webpack 有一个功能就是将你的代码库分割成 chunks(语块),当代码运行到需要它们的时候再进行加载。

适用的场景:

  • 抽离相同代码到一个共享块

  • 脚本懒加载,使得初始下载的代码更小

image-20221031234314107

懒加载 JS 脚本的方式

CommonJS: require.ensure
ES6:动态import (目 前还没有原生支持,需要babel转换)

如何使用动态 import?

代码分割的效果

image-20221101003950592

import React from 'react'
import ReactDOM from 'react-dom'
import img from '../images/img.jpeg'
import '../../common/index'

import './index.css'
import './style.less'

class Hello extends React.Component {
  constructor() {
    super(...arguments)
    this.state = {
      Text: null,
    }
  }

  loadComponent() {
    import('./text.js').then((Text) => {
      this.setState({
        Text: Text.default,
      })
    })
  }

  render() {
    const { Text } = this.state
    return (
      <div>
        {Text ? <Text /> : null}
        Hello {this.props.toWhat}
        <p>汉字 watch devServer 12345678</p>
        <img src={img} onClick={this.loadComponent.bind(this)} />
      </div>
    )
  }
}

const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(<Hello toWhat="World" />)

发送一个 Jsonp 请求,window[webpackJsonp] 异步加载逻辑

新版本 webpack5 是 (self["webpackChunkwebpack_learn"] = self["webpackChunkwebpack_learn"] || [])

根据我的 package name 来的,"name": "webpack-learn"

image-20221101004234265

32丨webpack和ESLint结合

image-20221108021024587

image-20221101005334648

ESL _int如何执行落地?
和CI/CD系统集成
和webpack集成

image-20221101005457333

本地开发阶段增加 precommit 钩子

方案二: webpack 与ESLint集成

33丨webpack打包组件和基础库

webpack打包库和组件

webpack除了可以用来打包应用,也可以用来打包js库

实现一个大整数加法库的打包

  • 需要打包压缩版和非压缩版本
  • 支持AMD/CJS/ESM模块引入

如何将库暴露出去?

library:指定库的全局变量

libraryTarget:支持库引入的方式

  {
    entry: {
      'large-number': './src/index.ts',
      'large-number.min': './src/index.ts',
    },
    output: {
      path: path.resolve(__dirname, 'dist'),
      filename: '[name].js',
      library: {
        name: 'largeNumber',
        type: 'umd',
        export: 'default',
      },
    },
      optimization: {
      minimize: true,
      minimizer: [
        new TerserPlugin({
          include: /\.min\.js$/,
        }),
      ],
    },
  }
{
  "main": "index.js",
  "script": {
     "prepublish": "yarn run build:prod"
  }
}

34丨webpack实现SSR打包(上)

image-20221110020622305

服务端渲染(SSR)是什么?

渲染:HTML+csS+JS+Data-->渲染后的HTML

服务端:

  • 所有模板等资源都存储在服务端
  • 内网机器拉取数据更快
  • 一个HTML返回所有数据

image-20221110020929988

image-20221110020948219

SSR的优势

减少白屏时间

对于SEO友好

image-20221110021033620

屏幕快照 2022-11-10 上午2.11.05

35丨webpack实现SSR打包(下)

如何解决样式不显示的问题?

  • 使用打包出来的浏览器端htm|为模板
  • 设置占位符,动态插入组件

首屏数据如何处理?

  • 服务端获取数据
  • 替换占位符
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title><%= htmlWebpackPlugin.options.title %></title>
  </head>
  <body>
    <div id="root"><!--HTML_PLACEHOLDER--></div>
    <!--INSTALL_DATA_PLACEHOLDER-->
  </body>
</html>
const express = require('express')
const fs = require('fs')
const path = require('path')
const { renderToString } = require('react-dom/server')

const template = fs.readFileSync(path.join(__dirname, '../dist/search.html'), 'utf-8')
const data = require('./data.json')
const SSR = require('../dist/search-server')

if (typeof self === 'undefined') {
  global.self = {}
}

const renderMarkUp = str => {
  const dataStr = JSON.stringify(data)
  return template
    .replace('<!--HTML_PLACEHOLDER-->', str)
    .replace(
      '<!--INSTALL_DATA_PLACEHOLDER-->',
      `<script>window.__install_data=${dataStr}</script>`
    )
}

const server = (port) => {
  const app = express()

  app.use(express.static('dist'))
  app.get('/search', (req, res) => {
    const html = renderToString(SSR)
    res.status(200).send(renderMarkUp(html))
  })

  app.listen(port, () => {
    console.log('Server is running on port: ', `http://localhost:${port}`)
  })
}

server(process.env.port || 3000)
'use strict'
const glob = require('glob')
const path = require('path')
// const webpack = require('webpack')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const ESLintPlugin = require('eslint-webpack-plugin')

const setMPA = () => {
  const entry = {}
  const htmlWebpackPlugins = []

  //  '/Users/xiaotian/Code/Case/webpack/learn2/src/index/index.js',
  //  '/Users/xiaotian/Code/Case/webpack/learn2/src/search/index.js'
  const entryFiles = glob.sync(path.join(__dirname, './src/*/index-server.js'))

  // eslint-disable-next-line array-callback-return
  Object.keys(entryFiles).map((index) => {
    const entryFile = entryFiles[index]

    const match = entryFile.match('/src/(.*)/index-server.js')
    const pageName = match && match[1]

    if (pageName) {
      entry[pageName] = entryFile

      htmlWebpackPlugins.push(
        new HtmlWebpackPlugin({
          template: path.join(__dirname, `src/${pageName}/index.html`),
          filename: `${pageName}.html`,
          chunks: ['vendors', 'commons', pageName],
          minify: {
            removeComments: false
          }
        })
      )
    }
  })

  return {
    entry,
    htmlWebpackPlugins
  }
}

const { entry, htmlWebpackPlugins } = setMPA()

module.exports = {
  entry,
  output: {
    path: path.join(__dirname, 'dist'),
    filename: '[name]-server.js',
    library: {
      type: 'umd'
    },
    globalObject: 'typeof self !== \'undefined\' ? self : this',
    publicPath: ''

  },
  mode: 'production',
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader'
        }
      },
      {
        test: /\.css$/i,
        use: [{
          loader: MiniCssExtractPlugin.loader,
          options: {
            publicPath: ''
          }
        },
        'css-loader']
      },
      {
        test: /\.less$/i,
        sideEffects: true,
        use: [
          // compiles Less to CSS
          {
            loader: MiniCssExtractPlugin.loader,
            options: {
              publicPath: ''
            }
          },
          'css-loader',
          'less-loader',
          {
            loader: 'postcss-loader'
          }
        ]
      },
      {
        test: /\.(png|jpe?g|gif)$/i,
        use: [
          {
            loader: 'file-loader',
            options: {
              name: '[name]_[hash:8].[ext]'
            }
          }
        ]
      },
      {
        test: /\.(woff|woff2|eot|ttf|otf)$/i,
        use: [
          {
            loader: 'file-loader',
            options: {
              name: '[name]_[hash:8].[ext]'
            }
          }
        ]
      }
    ]
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: '[name]_[contenthash:8].css'
    }),
    // new webpack.HotModuleReplacementPlugin()
    new CleanWebpackPlugin(),
    new ESLintPlugin()
  ].concat(htmlWebpackPlugins),
  devServer: {
    static: {
      directory: path.join(__dirname, 'dist')
    },
    compress: true,
    hot: true
  },
  optimization: {
    minimizer: [new CssMinimizerPlugin()],
    splitChunks: {
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/,
          name: 'vendors',
          chunks: 'all'
        },
        commons: {
          name: 'commons',
          chunks: 'all',
          minChunks: 2
        }
      }
    }
  }
}

36丨优化构建时命令行的显示日志

当前构建时的日志显示.

展示一大堆日志,很多并不需要开发者关注

Stats Presets

Webpack comes with certain presets available for the stats output:

Preset Alternative Description
'errors-only' none Only output when errors happen
'errors-warnings' none Only output errors and warnings happen
'minimal' none Only output when errors or new compilation happen
'none' false Output nothing
'normal' true Standard output
'verbose' none Output everything
'detailed' none Output everything except chunkModules and chunkRootModules
'summary' none Output webpack version, warnings count and errors count

如何优化命令行的构建日志

使用friendly-errors-webpack-plugin

  • success:构建成功的日志提示
  • warning:构建警告的日志提示
  • error:构建报错的日志提示
  • stats设置成errors- only

37丨构建异常和中断处理

如何判断构建是否成功?

在CI/CD的pipline 或者发布系统需要知道当前构建状态

每次构建完成后输入echo $?获取错误码

构建异常和中断处理

webpack4之前的版本构建失败不会抛出错误码(error code)

Node.js中的process.exit规范

  • 0表示成功完成,回调函数中,err为null
  • 非0表示执行失败,回调函数中,err 不为null, err.code 就是传给exit 的数字

如何主动捕获并处理构建错误?

compiler在每次构建结束后会触发 done 这个 hook

process.exit主动处理构建报错

image-20221111003141815

function () {
  this.hooks.done.tap('done', (stats) => {
    console.log(stats.errors)
    // process.exit(0)
  })
}

38丨构建配置包设计

构建配置抽离成npm包的意义

通用性

  • 业务开发者无需关注构建配置
  • 统一团队构建脚本

可维护性

  • 构建配置合理的拆分
  • README文档、ChangeLog文档等

质量

  • 冒烟测试、单元测试、测试覆盖率
  • 持续集成

构建配置管理的可选方案

通过多个配置文件管理不同环境的构建,webpack -- config参数进行控制

将构建配置设计成一个库,比如: hjs- -webpack、 Neutrino、 webpack- -blocks

抽成一个工具进行管理,比如: create-react- -app, kyt, nwb

将所有的配置放在一个文件,通过-- env参数控制分支选择

构建配置包设计

通过多个配置文件管理不同环境的webpack配置

  • 基础配置: webpack.base.js
  • 开发环境: webpack.dev.js
  • 生产环境: webpack.prod.js
  • SSR环境: webpack.ssr.js
  • ...

抽离成一个npm 包统一管理

  • 规范: Git commit日志、README、ESLint规范、Semver规范
  • 质量:冒烟测试、单元测试、测试覆盖率和CI

通过 webpack-merge 组合配置

https://www.npmjs.com/package/webpack-merge

39丨功能模块设计和目录结构

功能模块设计

image-20221111004830237

目录结构

lib放置源代码

test放置测试代码

image-20221111004951377

拆分不同环境的配置,通过 merge 合并

40丨使用ESLint规范构建脚本

使用ESL int规范构建脚本

使用eslint-config- -airbnb- base

eslint -- fix 可以自动处理空格

module.exports = {
  env: {
    browser: true,
    node: true,
    commonjs: true,
    es2021: true,
  },
  extends: 'airbnb-base',
  overrides: [
  ],
  parserOptions: {
    ecmaVersion: 'latest',
  },
  rules: {
  },
};

41丨冒烟测试介绍和实际运用

冒烟测试(smoke testing)

冒烟测试是指对提交测试的软件在进行详细深入的测试之前而进行的预测试,这种
预测试的主要目的是暴露导致软件需重新发布的基本功能失效等严重问题。

冒烟测试执行

构建是否成功

每次构建完成build目录是否有内容输出

  • 是否有JS、CSS等静态资源文件
  • 是否有HTML文件

判断构建是否成功

在示例项目里面运行构建,看看是否有报错

const path = require('path');
const webpack = require('webpack');
const rimraf = require('rimraf');
const Mocha = require('mocha');

const mocha = new Mocha({
  timeout: 10000,
});

process.chdir(path.join(__dirname, 'template'));

rimraf('./dist', () => {
  // eslint-disable-next-line global-require
  const prodConfig = require('../../lib/webpack.pord');

  webpack(prodConfig, (err, stats) => {
    if (err) {
      console.error(err);
      process.exit(2);
    }

    console.log(stats.toString({
      color: true,
      modules: false,
      children: false,
      chunks: false,
      chunkModules: false,
    }));
  });

  console.log('Webpack Build Success, begin run test.');

  // 不知道为什么 先执行了,延迟执行一下
  setTimeout(() => {
    mocha.addFile(path.join(__dirname, 'html-test.js'));
    mocha.addFile(path.join(__dirname, 'css-js-test.js'));

    mocha.run();
  }, 3000);
});

判断基本功能是否正常

编写mocha测试用例

  • 是否有JS、CSS等静态资源文件
  • 是否有HTML文件
const glob = require('glob-all');

// eslint-disable-next-line no-undef
describe('Checking generated html files', () => {
  // eslint-disable-next-line no-undef
  it('should generate html files', (done) => {
    const files = glob.sync(['./dist/index.html', './dist/search.html']);
    console.log('object', files);

    if (files.length > 0) {
      done();
    } else {
      throw new Error('no html files generated');
    }
  });
});
const glob = require('glob-all');

describe('Checking generated css js files', () => {
  it('should generate css js files', (done) => {
    const files = glob.sync([
      './dist/index_*.js',
      './dist/index_*.css',
      './dist/search_*.js',
      './dist/search_*.css',
    ]);

    if (files.length > 0) {
      done();
    } else {
      throw new Error('no css js files generated');
    }
  });
});

42丨单元测试和测试覆盖率

image-20221113000931871

image-20221113001238178

image-20221113001253129

43丨持续集成和TravisCI

image-20221113005128247

image-20221113005138720

接入Travis CI

  1. https:/ /travis- -ci.org/ 使用GitHub账号登录
  2. 在https:/ /travis- -ci.org/ account/ repositories为项目开启
  3. 项目根目录下新增.travis.ymI

image-20221113005339015

44丨发布到npm

发布到npm

添加用户: npm adduser

升级版本

​ 升级补丁版本号: npm version patch
​ 升级小版本号: npm version minor
​ 升级大版本号: npm version major

发布版本: npm publish

45丨Git丨Commi规范和changelog生成

image-20221113005720379

image-20221113005749636

image-20221113005822212

image-20221113005835285

46丨语义化版本(Semantic丨Versioning)规范格式

image-20221113010204690

image-20221113010020618

image-20221113010111239

image-20221113010130190

47丨初级分析:使用webpack内置的stats

stats:构建的统计信息

package.json 中使用 stats

"build:stats": "webpack --config webpack.prod.js --json > stats.json",

image-20221113011416349

48丨速度分析:使用speed-measure-webpack-plugin

image-20221113011901608

速度分析插件作用

分析整个打包总耗时

每个插件和loader的耗时情况

49丨体积分析:使用webpack-bundle-analyzer

image-20221113012823423

可以分析哪些问题?

依赖的第三方模块文件大小

业务里面的组件代码大小

50丨使用高版本的webpack和Node

image-20221113013737079

image-20221113013812707

51丨多进程多实例构建

image-20221113015946094

image-20221113015956399

image-20221113020013496

52丨多进程多实例并行压缩

image-20221113020527310

image-20221113020542399

image-20221113020552299

推荐

53丨进一步分包:预编译资源模块

image-20221113023141437

进一步分包:预编译资源模块

思路:将react、react- dom、redux、 react- redux 基础包和业务基础包打包成一个文件

方法:使用DLLPlugin进行分包,DllReferencePlugin 对manifest.json弓|用

image-20221113023225717

image-20221113023242507

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

module.exports = {
  entry: {
    library: ['react', 'react-dom']
  },
  output: {
    filename: '[name]_[hash].ddl.js',
    path: path.join(__dirname, 'build/library'),
    library: '[name]'
  },
  plugins: [
    new webpack.DllPlugin({
      name: '[name]_[hash]',
      path: path.join(__dirname, 'build/library/[name].json')
    })
  ]
}

54丨充分利用缓存提升二次构建速度

缓存

目的:提升二次构建速度

缓存思路:

  • babel-loader开启缓存
  • terser- -webpack- -plugin 开启缓存
  • 使用cache-loader或者hard- source-webpack-plugin

55丨缩小构建目标

缩小构建目标

目的:尽可能的少构建模块

比如babel-loader 不解析node_ modules

image-20221113023958678

减少文件搜索范围

优化resolve.modules配置(减少模块搜索层级)

优化resolve.mainFields配置

优化resolve.extensions 配置

合理使用alias

image-20221113024036424

56丨使用webpack进行图片压缩

图片压缩
要求:基于Node库的imagemin或者tinypng API
使用:配置image- -webpack-loader

rules: [{
  test: /\.(gif|png|jpe?g|svg)$/i,
  use: [
    'file-loader',
    {
      loader: 'image-webpack-loader',
      options: {
        mozjpeg: {
          progressive: true,
        },
        // optipng.enabled: false will disable optipng
        optipng: {
          enabled: false,
        },
        pngquant: {
          quality: [0.65, 0.90],
          speed: 4
        },
        gifsicle: {
          interlaced: false,
        },
        // the webp option will enable WEBP
        webp: {
          quality: 75
        }
      }
    },
  ],
}]

image-20221121142259358

57丨使用TreeShaking擦除无用的CSS

tree shaking(摇树优化)复习

概念: 1个模块可能有多个方法,只要其中的某个方法使用到了,则整个文件都会被打到
bundle里面去,tree shaking就是只把用到的方法打入bundle,没用到的方法会在
uglify阶段被擦除掉。

使用: webpack默认支持,在.babelrc里设置modules: false即可

  • production mode的情况下默认开启

要求:必须是ES6的语法,CJS的方式不支持

无用的CSS如何删除掉?

PurifyCSS:遍历代码,识别已经用到的CSS class

uncss: HTML需要通过jsdom加载,所有的样式通过PostCSS解析,通过
document.querySelector来识别在html文件里面不存在的选择器

image-20221130014349452

58丨使用动态Polyfill服务

image-20221130014628362

image-20221130015150972

image-20221130015209644

image-20221130015431686

image-20221130015552975

image-20221130015711356

59丨webpack启动过程分析

image-20221130015853825

查找webpack入口文件

在命令行运行以上命令后,npm会让命令行工具进入node_ modules.bin 目录
查找是否存在webpack.sh或者webpack.cmd文件,如果存在,就执行,不
存在,就抛出错误。

实际的入口文件是: node_ modules \webpack \bin\webpack.js

"bin": {
  "webpack": "bin/webpack.js"
},

image-20221130020232562

"version": "5.75.0",

const cli = {
	name: "webpack-cli",
	package: "webpack-cli",
	binName: "webpack-cli",
	installed: isInstalled("webpack-cli"),
	url: "https://github.com/webpack/webpack-cli"
};

if (!cli.installed) {
		//...
		process.exitCode = 0;

		//...

		runCommand(packageManager, installOptions.concat(cli.package))
			.then(() => {
				runCli(cli);
			})
			.catch(error => {
				console.error(error);
				process.exitCode = 1;
			});
	});
} else {
	runCli(cli);
}

启动后的结果

webpack最终找到webpack- -cli (webpack- -command) 这个npm包,并且执行CLI

60丨webpack-cli源码阅读

webpack- -cli做的事情

引入yargs,对命令行进行定制

分析命令行参数,对各个参数进行转换,组成编译配置项

引用webpack,根据配置项进行编译和构建

从NON_ COMPIL ATION_ _CMD分析出不需要编译的命令

webpack- cli处理不需要经过编译的命令

image-20221130021319443

image-20221130021657625

image-20221130021744065

image-20221130022025149

webpack-cli执行的结果

webpack--cli对配置文件和命令行参数进行转换最终生成配置选项参数options

最终会根据配置参数实例化webpack对象,然后执行构建流程

61丨Tapable插件架构与Hooks设计

Webpack的本质

Webpack可以将其理解是一种基于事件流的编程范例,一 系列的插件运行。

核心对象Compiler继承Tapable

核心対象Compilation継承Tapable

class Compiler extends Tapable {}

class Compilation extends Tapable {}

Tapable是什么?

Tapable是一个类似于Node.js的EventEmitter的库,主要是控制钩子函数的发布与订阅,控制着webpack的插件系统。

Tapable库暴露了很多Hook (钩子)类,为插件提供挂载的钩子

const {
	SyncHook, //同步钩子
	SyncBailHook, //同步熔断钩子
	SyncWaterfallHook, //同步流水钩子
	SyncLoopHook, //同步循环钩子
	AsyncParallelHook, 从异步并发钩子
	AsyncParallelBailHook, 4异步并发熔断钩子
	AsyncSeriesHook, //异步串行钩子
	AsyncSeriesBailHook, //异步串行熔断钩子
	AsyncSeriesWaterfallHook //异步串行流水钩子
} = require("tapable'");

Tapable hooks 类型

image-20221130123803677

Tapable的使用-new Hook新建钩子

Tapable暴露出来的都是类方法,new一个类方法获得我们需要的钩子

class接受数组参数options,非必传。类方法会根据传参,接受同样数量的参数。
const hook1 = new SyncHook(["arg1", "arg2", "arg3"]);

Tapable的使用-钩子的绑定与执行

Tabpack提供了同步&异步绑定钩子的方法,并且他们都有绑定事件和执行事件对应的方法。

Async* Sync*
绑定: tapAsync/tapPromise/tap 绑定: tap
执行: callAsync/promise 执行: call

Tapable的使用-hook基本用法示例

const hook1 = new SyncHook(["arg1", "arg2", "arg3"]);

//绑定事件到webapck事件流
hook1.tap('hook1', (arg1, arg2, arg3) => console.log(arg1, arg2, arg3)) //1,2,3

//执行绑定的事件
hook1.call(1,2,3)

Tapable的使用-实际例子演示

定义一个Car方法,在内部hooks.上新建钩子。分别是同步钩子accelerate、brake (accelerate接受一个参数)、 异步 钩子calculateRoutes

使用钩子对应的绑定和执行方法

calculateRoutes使用tapPromise可以返回一个promise对象。

const { SyncHook } = require('tapable')

const hook = new SyncHook(['arg1', 'arg2', 'arg3'])

hook.tap('hook1', (arg1, arg2, arg3) => {
  console.log(arg1, arg2, arg3)
})

hook.call(1, 2, 3)
const {
  SyncHook,
  SyncBailHook,
  SyncWaterfallHook,
  SyncLoopHook,
  AsyncParallelHook,
  AsyncParallelBailHook,
  AsyncSeriesHook,
  AsyncSeriesBailHook,
  AsyncSeriesWaterfallHook
} = require("tapable");

class Car {
  constructor() {
    this.hooks = {
      accelerate: new SyncHook(["newSpeed"]),
      brake: new SyncHook(),
      calculateRoutes: new AsyncParallelHook(["source", "target", "routesList"])
    };
  }
}

const myCar = new Car();

// 绑定同步钩子
// Use the tap method to add a consument
myCar.hooks.brake.tap("WarningLampPlugin", () => console.log('WarningLampPlugin'));

// 绑定同步钩子
myCar.hooks.accelerate.tap("LoggerPlugin", newSpeed => console.log(`Accelerating to ${newSpeed}`));

// 绑定一个异步 Promise 钩子
myCar.hooks.calculateRoutes.tapPromise("calculateRoutes tapPromise", (source, target, routesList, callback) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log(`tapPromise too ${source} ${target} ${routesList}`);
      resolve()
      console.log('end');
    }, 1000)
  })
});

myCar.hooks.brake.call()
myCar.hooks.accelerate.call(10)


console.time('coost');

// 执行异步钩子
myCar.hooks.calculateRoutes.promise('Async', 'hook', 'demo').then(() => {
  console.log('resolve')
  console.timeEnd('coost');
}, err => {
  console.error(err);
  console.timeEnd('coost');
})
WarningLampPlugin
Accelerating to 10
tapPromise too Async hook demo
end
resolve
coost: 1.005s

62丨Tapable是如何和Webpack进行关联起来的?

image-20221130145802648

模拟 Compiler.js

const { SyncHook, AsyncParallelHook } = require("tapable");

module.exports = class Compiler {
  constructor() {
    this.hooks = {
      accelerate: new SyncHook(["newSpeed"]),
      brake: new SyncHook(),
      calculateRoutes: new AsyncParallelHook(["source", "target", "routesList"])
    };
  }

  run() {
    this.accelerate(10)
    this.brake()
    this.calculateRoutes('Async', 'hook', 'demo')
  }


  accelerate(speed) {
    this.hooks.accelerate.call(speed)
  }

  brake() {
    this.hooks.brake.call()
  }

  calculateRoutes() {
    this.hooks.calculateRoutes.promise('Async', 'hook', 'demo').then(() => {
      console.log('resolve')
      console.timeEnd('coost');
    }, err => {
      console.error(err);
      console.timeEnd('coost');
    })
  }
}

插件 my-plugin.js

const Compiler = require('./compiler')

class MyPlugin {
  constructor() {}

  apply(compiler) {
    // 绑定同步钩子
    // Use the tap method to add a consument
    compiler.hooks.brake.tap('WarningLampPlugin', () =>
      console.log('WarningLampPlugin')
    )

    // 绑定同步钩子
    compiler.hooks.accelerate.tap('LoggerPlugin', (newSpeed) =>
      console.log(`Accelerating to ${newSpeed}`)
    )

    // 绑定一个异步 Promise 钩子
    compiler.hooks.calculateRoutes.tapPromise(
      'calculateRoutes tapPromise',
      (source, target, routesList, callback) => {
        return new Promise((resolve, reject) => {
          setTimeout(() => {
            console.log(`tapPromise too ${source} ${target} ${routesList}`)
            resolve()
            console.log('end')
          }, 1000)
        })
      }
    )
  }
}

模拟插件执行

const myPlugin = new MyPlugin()

const options = {
  plugins: [myPlugin],
}

const compiler = new Compiler()

for (const plugin of options.plugins) {
  if (typeof plugin === 'function') {
    plugin.call(compiler, compiler)
  } else {
    plugin.apply(compiler)
  }
}

compiler.run()
Accelerating to 10
WarningLampPlugin
tapPromise too Async hook demo
end
resolve

63丨webpack流程篇:准备阶段

image-20221130160916966

WebpackOptionsApply

将所有的配置options 参数转换成webpack内部插件

使用默认插件列表

举例:

  • output.library -> LibraryTemplatePlugin
  • externals -> ExternalsPlugin
  • devtool -> EvalDevtoolModulePlugin, SourceMapDev ToolPlugin
  • AMDPlugin, CommonJsPlugin
  • RemoveEmptyChunksPlugin

64丨webpack流程篇:模块构建和chunk生成阶段

Compiler hooks

流程相关:

  • (before-)run
  • (before-/ after-)compile
  • make
  • (after- -)emit
  • done

监听相关:

  • watch-run
  • watch-close

Compilation

Compiler 调用 Compilation 生命周期方法

  • addEntry - -> addModuleChain
  • finish (上报模块错误)
  • seal

image-20221130181843918

image-20221130182026306

NormalModule

Build

  • 使用 loader-runner 运行 loaders
  • 通过Parser解析(内部是acron)
  • ParserPlugins 添加依赖

image-20221130183433506

Chunk生成算法

  1. webpack先将entry中对应的module都生成一个 新的chunk
  2. 遍历module的依赖列表,将依赖的module也加入到chunk中
  3. 如果一个依赖module是动态引入的模块,那么就会根据这个module创建一个新的chunk,继续遍历依赖
  4. 重复.上面的过程,直至得到所有的chunks

65丨webpack流程篇:文件生成

this.hooks.emit.callAsync(compilation, err => {
  if (err) return callback(err);
  outputPath = compilation.getPath(this.outputPath, {});
  mkdirp(this.outputFileSystem, outputPath, emitFiles);
});

66丨动手编写一个简易的webpack(上)

模块化:增强代码可读性和维护性

传统的网页开发转变成Web Apps开发

代码复杂度在逐步增高

分离的JS文件/模块,便于后续代码的维护性

部署时希望把代码优化成几个HTTP请求

常见的几种模块化方式

ES module

import * as largeNumber from 'large-number';
// ...
largeNumber.add('999','1');

CJS

const largeNumbers = require('large-number');
// ...
largeNumber.add('999','1');

AMD

require(['large-number'], function (large-number){
  // ...
  largeNumber.add('999','1');
}

AST基础知识

在计算机科学中,抽象语法树(Abstract Syntax Tree,AST),或简称语法树(Syntax tree),是源代码语法结构的一种抽象表示。 它以树状的形式表现编程语言的语法结构,树上的每个节点都表示源代码中的一种结构。 之所以说语法是“抽象”的,是因为这里的语法并不会表示出真实语法中出现的每个细节。

DEMO: https://esprima.org/demo/parse.html

image-20221130185124373

image-20221130185450729

动手实现一个简易的webpack

可以将ES6语法转换成ES5的语法

  • 通过babylon生成AST
  • 通过babel-core将AST重新生成源码

可以分析模块之间的依赖关系

  • 通过babel-traverse的ImportDeclaration方法获取依赖属性

生成的JS文件可以在浏览器中运行

67丨动手编写一个简易的webpack(下)

const Compiler = require('./compiler')
const options = require('../simplepack.config')

new Compiler(options).run()
const { getAST, getDependencies, transform } = require('./parser')
const path = require('path');
const fs = require('fs');

module.exports = class Compiler {
  
  // 接收通过lib/index.js new Compiler(options).run()传入的参数,对应`forestpack.config.js`的配置
  // 接收forestpack.config.js配置参数,并初始化entry、output
  constructor(options) {
    const { entry, output } = options;

    this.entry = entry
    this.output = output
    this.modules = []
  }

  // 开启编译
  // 开启编译run方法。处理构建模块、收集依赖、输出文件等。
  run() {
    const entryModule = this.buildMModule(this.entry, true)

    console.log('entryModule', entryModule)

    this.modules.push(entryModule)

    this.modules.map((_module) => {
      _module.dependencies.map((dependency) => {
        this.modules.push(this.buildMModule(dependency))
      })
    })

    console.log(this.modules)

    this.emitFiles()
  }

  // 构建模块相关
    // filename: 文件名称
    // isEntry: 是否是入口文件
  // buildModule方法。主要用于构建模块(被run方法调用)
  buildMModule(filename, isEntry) {
    let ast

    if (isEntry) {
      ast = getAST(filename)
    } else {
      const absolutePath = path.join(process.cwd(), './src', filename)

      console.log('buildMModule absolutePath', absolutePath, isEntry);

      ast = getAST(absolutePath)
    }

    return {
      filename, // 文件名称
      dependencies: getDependencies(ast), // 依赖列表
      source: transform(ast) // 转化后的代码
    }
  }

  // 输出文件
  // emitFiles方法。输出文件(同样被run方法调用)
  emitFiles() {
    const outputPath = path.join(this.output.path, this.output.filename)

    let modules = ''

    this.modules.map((_module) => {
      modules += `'${_module.filename}': function(__webpack_require__, module, exports) { ${_module.source} },`
    })

    const bundle = `(function(modules) {
      // 模块加载函数
      function __webpack_require__(filename) {
        let fn = modules[filename];
        let module = { exports: {} }

        fn(__webpack_require__, module, module.exports)

        return module.exports
      }

      __webpack_require__('${ this.entry }')
    })({${ modules }})`

    console.log('bundle.js', bundle)

    fs.writeFileSync(outputPath, bundle, 'utf-8')
  }
}
const fs = require('fs');
// const babylon = require("babylon");
const { parse } = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const { transformFromAst } = require('@babel/core');

module.exports = {
  // 解析我们的代码生成AST抽象语法树
  getAST: (path) => {
    const source = fs.readFileSync(path, 'utf8')

    return parse(source, {
      // parse in strict mode and allow module declarations
      sourceType: "module", //表示我们要解析的是ES模块
    });
  },

  // 对AST节点进行递归遍历
  getDependencies: (ast) => {
    const dependencies = []

    traverse(ast, {
      ImportDeclaration: ({node}) => {
        dependencies.push(node.source.value)
      }
    });

    return dependencies
  },

  // 将获得的ES6的AST转化成ES5
  transform: (ast) => {
    const { code } = transformFromAst(ast, null, { "presets": ["@babel/preset-env"] });
    // console.log('result', result);
    return code
  }
}
;(function (modules) {
  // 模块加载函数
  function __webpack_require__(filename) {
    let fn = modules[filename]
    let module = { exports: {} }

    fn(__webpack_require__, module, module.exports)

    return module.exports
  }

  __webpack_require__(
    '/Users/xiaotian/Code/Case/webpack/simplepack/src/index.js'
  )
})({
  '/Users/xiaotian/Code/Case/webpack/simplepack/src/index.js': function (
    __webpack_require__,
    module,
    exports
  ) {
    'use strict'

    var _greeting = require('./greeting.js')
    document.write((0, _greeting.greeting)('Jane'))
  },
  './greeting.js': function (__webpack_require__, module, exports) {
    'use strict'

    Object.defineProperty(exports, '__esModule', {
      value: true,
    })
    exports.greeting = void 0
    var greeting = function greeting(name) {
      return 'hello ' + name
    }
    exports.greeting = greeting
  },
})
;(function (modules) {
  // 模块加载函数
  function __webpack_require__(filename) {
    let fn = modules[filename]
    let module = { exports: {} }

    fn(__webpack_require__, module, module.exports)

    return module.exports
  }

  __webpack_require__(
    '/Users/xiaotian/Code/Case/webpack/simplepack/src/index.js'
  )
})({
  '/Users/xiaotian/Code/Case/webpack/simplepack/src/index.js': function (
    __webpack_require__,
    module,
    exports
  ) {
    'use strict'

    var _greeting = require('./greeting.js')
    document.write((0, _greeting.greeting)('Jane'))
  },
  './greeting.js': function (__webpack_require__, module, exports) {
    'use strict'

    Object.defineProperty(exports, '__esModule', {
      value: true,
    })
    exports.greeting = void 0
    var greeting = function greeting(name) {
      return 'hello ' + name
    }
    exports.greeting = greeting
  },
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant