24 примера для начала работы и освоения "Webpack4" (3)

Webpack

следующий24 примера, чтобы начать работу и освоить "Webpack4" (2)Следовать за:

  1. Конфигурация ПВА
  2. Конфигурация TypeScript
  3. Конфигурация Эслинта
  4. Используйте DLLPlugin для ускорения упаковки
  5. Конфигурация многостраничной упаковки
  6. загрузчик записи
  7. Написать плагин
  8. Написать пакет

Семнадцать, конфигурация PWA

адрес исходного кода demo17

В этом разделе используетсяdemo15на основе кода

Давайте смоделируем операцию предотвращения отправки упакованного кода на сервер в обычной разработке, сначала упакуем кодnpm run build

затем установите плагинnpm i http-server -D

Настройте команду скрипта в package.json

{
  "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

Теперь служба запущена, порт 8080, теперь доступhttp://127.0.0.1:8080вы можете увидеть эффект

Если вы запускаете другие проекты и порт также 8080, порт будет конфликтовать.Не забудьте сначала закрыть порт 8080 других проектов, а затемnpm run start

Нажимаем ctrl+c, чтобы закрыть http-сервер для имитацииСервер зависаетСцена, Revisit.http://127.0.0.1:8080это будет так

Страница недоступна, потому что наш сервер не работает. Что такое технология 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.jsа такжеprecache-manifest.jsДва файла, с помощью которых наша веб-страница может поддерживать технологию PWA,service-worker.jsМожно понимать как альтернативный кеш

Его также необходимо использовать в бизнес-коде.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, откройте консоль

Теперь, когда файл кэширован, нажмите ctrl + c, чтобы закрыть службу, и снова обновите страницу.

Конфигурация TypeScript

адрес исходного кода demo18

TypeScriptэто расширенный набор типов JavaScript, который компилируется в чистый JavaScript

новая папка,npm init -y,npm 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

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

бегатьnpm ruh build, сообщил об ошибке, отсутствуетtsconfig.jsonдокумент

При упаковке машинописных файлов необходимо создать файл tsconfig.json в корневом каталоге проекта.

Ниже приведена простая конфигурация, см. подробнееОфициальный сайт

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

Снова упакуйте, откройте файл bundle.js,Скопируйте весь код в консоль браузера, используя этот код, вы можете увидеть, что Hello, world появляется во всплывающем окне, указывая на то, что ts был успешно скомпилирован и упакован

Импорт сторонних библиотек

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 требует от нас передачи параметров, но теперь мы ничего не передаем и об ошибке не сообщается.Мы используем typescript для проверки типов, что также можно сделать при внедрении сторонних библиотек, но теперь ошибки нет или подскажите.

Нам также необходимо установить плагин машинописного текста для lodash, чтобы параметры в методе lodash можно было распознать, и при неправильном использовании выдавалось сообщение об ошибке.

npm i @types/lodash -D

После установки вы можете найти ошибку подчеркивания _

необходимо изменить наimport * as _ from 'lodash', удалите параметры, переданные методом соединения, и вы также можете найти отчет об ошибках метода соединения, который отражает преимущества машинописного текста.Точно так же, когда вводится jQuery, следует также ввести подключаемый модуль типа, соответствующий jQuery.

Как узнать, что используемая библиотека требует установки плагина соответствующего типа?

ОткрытьTypeSearch, здесь соответственно ищите библиотеку, которую хотите использовать, если есть какой-то плагин типа, если да, то нужно толькоnpm i @types/jquery -DТолько что

Девятнадцать, конфигурация Эслинта

адрес исходного кода demo19

Создайте пустую папку,npm init -y,npm webpack webpack-cli -DЗапустите, затем установите зависимости eslint

npm i eslint -D

Используйте npx для запуска eslint в этом проекте для инициализации конфигурации,npx eslint --init

Будет выбор 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

Просто напишите код в index.js для тестирования eslint.

eslint сообщает об ошибке, но переменная не используется после определения переменной.Если в редакторе нет сообщения об ошибке, вам необходимо сначала установить расширение eslint в vscode, оно будет основано на вашем текущем каталоге..eslintrc.jsфайл как правило проверки

Вы также можете использовать командную строку, чтобы позволить eslint проверять файлы во всем каталоге src.

Если вы считаете правило очень проблематичным и хотите его заблокировать, вы можете сделать это в соответствии с сообщением об ошибке eslint, таким как приведенное выше.no-unused-vars, скопируйте это правило в.eslintrc.jsНастройте его в правилах в"no-unused-vars": 0, 0 означает отключено, после сохранения сообщение об ошибке не будет, но этот метод подходит дляглобальная конфигурация, если вы хотите заблокировать проверку eslint только для определенной строки кода, вы можете сделать это

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

Это расширение eslint vscode не имеет ничего общего с webpack.То, о чем мы сейчас поговорим, это как использовать eslint в webpack.Сначала установите плагин

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: ['babel-loader', 'eslint-loader']

Порядок выполнения правил — справа налево и снизу вверх, сначала проверяется eslint на соответствие кода спецификации, а затем передается через babel

После настройки webpack.config.js восстанавливаем index.js до состояния предыдущего отчета об ошибке, не используем комментарии для отключения проверки, а затем запускаем команду упаковки, не забываем зайти в package.json для настройки скрипта

При упаковке будет указано, что код является неквалифицированным, можно настроить не только производственную среду, но и среду разработки, вы можете настроить eslint-loader в общедоступном модуле веб-пакета, что более удобно для нас для проверки кода Технические характеристики

Например: установите для исправления значение true, оно автоматически исправит некоторые ошибки для вас, если вы не можете исправить это автоматически, вам все равно нужно исправить это вручную.

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

Что касается eslint-loader, официальный сайт webpack также даетнастроить, друзья, кому интересно, идем смотреть сами

20. Используйте DLLPlugin для ускорения упаковки

адрес исходного кода demo20

В этом разделе используетсяdemo15на основе кода

Давайте сначала установим плагин lodashnpm i lodash, и напишите в файле app.js

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

Создайте новый файл 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, Для тех, кто забыл, вы можете просмотреть содержимое библиотеки пользовательских функций в разделе 16. Определение библиотеки эквивалентно монтированию этой глобальной переменной.Пока вы вводите имя глобальной переменной в консоли, вы можете отображать содержание внутри, например, здесь мыlibrary: '[name]'Соответствующее имя — это то, что мы определили в записиvendors

Добавьте еще одну команду в скрипт в package.json

{
  "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

Откройте файл, и вы обнаружите, что lodash был упакован в файл dll.

Так как же нам использовать этот файл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чтобы открыть веб-страницу

Теперь мы упаковали сторонний модуль в 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

Измените файл webpack.base.conf.js и добавьтеwebpack.DllReferencePluginплагин

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

Затем, когда веб-пакет упаковывается, вы можете комбинировать предыдущие глобальные переменныеvendorsи это вновь созданноеvendors.manifest.jsonОтображение файлов, а затем анализ нашего исходного кода, как только анализ использования сторонних библиотек находится вvendors.dll.js, буду использоватьvendors.dll.js, не буду использовать/node_modules/сторонняя библиотека

упаковать сноваnpm run build, вы можете поставитьwebpack.DllReferencePluginПосле того, как модуль аннотирован, он упаковывается и сравнивается

Около 4000 мс до комментария и около 4300 мс после комментария, хотя это всего на 300 мс быстрее, но сейчас мы только экспериментальная демонстрация.В реальных проектах, таких как 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

можно упаковать передvendors.dll.jsа такжеvendors.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, и посмотрите, успешно ли он работает

Это просто разделение двух сторонних модулей, и нам нужно настроить их один за другим. Есть ли способ сделать это проще? Да!

Здесь модули API и fs узла используются для чтения содержимого папки, а массив плагинов создается для хранения общедоступных плагинов.

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)

После написания можно сначала вывести, закомментировать плагины,npm run buildУпакуйте и посмотрите на выходное содержимое, вы можете видеть, что содержимое в папке распечатывается в виде массива, а затем мы можем выполнять некоторые циклические операции с этим массивом.

Полный код:

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на основе кода

Создайте новый файл list.js в каталоге src и напишите в немconsole.log('这里是 list 页面')

Настройте запись в webpack.base.conf.js, настройте две записи

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

Если теперь мы прямоnpm run buildУпаковка: в автоматически сгенерированном файле index.html вы обнаружите, что list.js также представлен, что указывает на то, что упаковка с несколькими элементами выполнена успешно, но не реализована.несколько страницПакет, который я хочу упаковатьindex.htmlа такжеlist.htmlДве страницы, представленные в index.htmlapp.js, представленный в list.htmllist.js,Как сделать?

Для удобства демонстрации сначалаwebpack.prod.conf.jsсерединаcacheGroupsдобавить одинdefaultатрибут, пользовательское имя

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' })
]

Два html генерируются в упакованном каталоге dist

Откройте index.html, и вы увидите, что app.js импортирован, а list.html импортирован. List.js, этоHtmlWebpackPluginплагинchunksатрибут, пользовательский импортированный js

Если вы хотите упаковать три страницы, перейдите к копированиюHtmlWebpackPlugin, настроив в записи, если их четыре или пять, копировать вручную хлопотнее, можно написать метод для автоматической генерации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

После повторной упаковки эффект тот же.Если вы хотите добавить страницы, вам нужно только ввести еще один файл js в запись в качестве записи.

Многостраничная конфигурация фактически предназначена для определения нескольких записей и создания нескольких html-страниц с помощью htmlWebpackPlugin.

Двадцать два, напиши загрузчик

адрес исходного кода demo22

новая папка,npm init -y,npm 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 это наш исходник, здесь нужно заменить мир в исходнике на 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'
  }
}

Структура каталога:

После упаковки открываем файл dist/main.js Внизу видно, что мир изменен на загрузчик, и написан простейший загрузчик.

Добавить свойство параметров

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)
}

Файл, созданный после упаковки, также изменяется на имя, указанное в параметрах.

Дополнительные настройки см. на официальном сайтеAPI, найдите интерфейс загрузчика, который имеет this.query

Если ваши параметры не являются объектом, а записаны в виде строки, могут быть некоторые проблемы, что здесь официально рекомендуетсяloader-utilsчтобы получить содержимое опций

Установить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)Результат согласован

Если вы хотите передать дополнительную информацию, возврат не прост в использовании, официальный сайт предоставляет намthis.callbackAPI, использование выглядит следующим образом

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 (может быть любой контент (например, некоторые метаданные)) в настоящее время не используются.Возвращается только результат, а после сохранения и переупаковки эффект и возвращение такое же

Что если написать асинхронный код в загрузчике

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)
}

Ошибка загрузчика не вернулась, используйте его здесь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)
}

Симулируйте синхронный загрузчик и асинхронный загрузчик

создать новый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Порядок выполнения загрузчика снизу, сначала выполнить асинхронный код, изменить 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, использование нескольких загрузчиков тоже делается

Если есть несколько пользовательских загрузчиков, проходите каждый разpath.resolve(__dirname, xxx)Есть ли лучший способ написать так?

использоватьresolveLoader, определите модули, когда вы используете загрузчик, он будет идти первым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'
  }
}

Двадцать три, напиши плагин

адрес исходного кода demo23

Во-первых, создайте новую папку, и npm запустит операцию.Особенности были упомянуты в предыдущих разделах, поэтому я не буду их повторять.

Создайте новую папку plugins в корневом каталоге, создайте новыйcopyright-webpack-plugin.js, обычно мы используемxxx-webpack-plugin, поэтому мы называем его так, определение плагина — это класс

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

module.exports = CopyrightWebpackPlugin

Используется в webpack.config.js, поэтому каждый раз, когда вы используете плагин, вам нужно использоватьnew, потому что плагин по сути является классом

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'
  }
}

После сохранения и упаковки плагин используется, но мы ничего не делали

Если мы хотим передать параметры, мы можем сделать это

new CopyrightWebpackPlugin({
  name: 'xh'
})

в то же времяcopyright-webpack-plugin.jsПолучено

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

module.exports = CopyrightWebpackPlugin

Давайте положимconstructorЗакомментируйте результат, который будет упакован,перед помещением в каталог distВ этот момент давайте проделаем некоторые операции

apply(compiler) {}компилятор можно рассматривать как экземпляр веб-пакета, подробности см. на официальном сайте.compiler-hooks

Хуки есть хуки, как жизненный цикл vue и реагировать, находитьemitВ этот момент упакованный результат выполняется перед тем, как поместить его в каталог dist.AsyncSeriesHookасинхронный метод

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

module.exports = CopyrightWebpackPlugin

потому чтоemitдаасинхронныйДа, черезtapAsyncЧтобы написать, когда вы помещаете код в каталог dist, этот хук сработает и перейдет к определенной нами функции, если вы используетеtapAsyncфункция, не забудьте использовать ее в концеcb(), Tapasync должен пройти два параметра, первый параметр передает имя плагина, который мы определены

После сохранения снова упаковать, и контент, который мы написали, также выводится

compilationВ этом параметре хранится все содержимое данного пакета, его можно вывестиcompilation.assetsпосмотри

Возвращаемый результат - объект,main.jsЭто ключ, то есть имя файла и суффикс файла, сгенерированный после упаковки, мы можем его имитировать

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

Генерируется в каталоге distcopyright.txtдокумент

Теперь представлен асинхронный хук, теперь используйте синхронизирующий хук

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

24. Написать пакет

адрес исходного кода demo24

модульный анализ

Создайте три новых файла в каталоге srcword.js,message.js,index.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')

используя узелfsмодуль, прочитать информацию о файле и вывести ее на консоль, здесь глобально установлен плагин для отображения подсветки кода,npm i cli-highlight -g,бегатьnode bundle.js | highlight

Код в index.js выведен на консоль, код выделен для удобства чтения Чтение информации о файле записи завершено

Теперь мы хотим прочитать зависимость message.js, используемую в файле index.js,import message from './message.js'

Установите сторонний плагинnpm i @babel/parser

@babel/parser— парсер JavaScript, используемый в Babel.

Официальный веб-сайт также предоставляет соответствующий пример кода, в соответствии с образцом кода для имитации, изменения наших файлов.

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, поэтомуsourceType: 'module'

После сохранения и запуска вывод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, и тип первого узлаImportDeclaration(введено объявление), по сравнению с тем, что мы написали в index.jsimport message from './message.js', тип второго узлаExpressionStatement(объявление выражений), по сравнению с тем, что мы написалиconsole.log(message)

Используйте babel, чтобы помочь нам сгенерировать абстрактное синтаксическое дерево, и мы импортируем егоimport message1 from './message1.js'бежать снова

Абстрактное синтаксическое дерево преобразует наш js-код в форму объекта, и теперь мы можем пройтись по типу в объекте узла, сгенерированному абстрактным синтаксическим деревом, независимо от того, является ли он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')

напечатал только два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.jsmessage1.jsЗависимости удалены.Вот примечание.Распечатанный путь к файлуотносительный путь, относительноsrc/index.jsфайл, но когда мы его упаковываем, это не может быть относительный путь файла записи (index.js), а должен бытьотносительный путь к корневому каталогу(или скорееабсолютный путь), с помощью API узла введите путь

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, результат печати вывода консоли

можно увидетьgraphArrayВсего в проекте три объекта, то есть три файла, которые мы ввели в проект, все они проанализированы.Для удобства чтения мы создаем объект графика и выкладываем результаты анализа по очереди.

// 依赖图谱
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для:

Наконец вmakeDependenciesGraphгенерал-лейтенантgraphвернуть, назначитьgraphInfo, вывод такой же, как график

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

сгенерировать код

Теперь, когда все результаты генерации кода получены, мы можем теперь использоватьЗависимостиГрафикдля генерации кода, который фактически работает в браузере

Лучше поставить его в большую закрытие.Избегайте загрязнения глобальной окружающей среды

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

    })(${graph})
  `
}

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

Сначала я отформатировал код выходного графика здесь, вы можете найти его вindex.jsиспользовалrequireметод,message.jsиспользуется не только вrequireметод, также используйтеexportsObject, но в браузере таких нет, если зайти напрямую, то сообщит об ошибке.

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 и экспорт объекта.

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 является графом зависимостей и выполняет его после получения записи./src/index.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);
      `
  }
}

Чтобы получить код в коде для выполнения, здесьиспользовать другое закрытие, пусть код в каждом модуле выполняется в замыкании, чтобы переменные модуля не влияли на внешние переменные

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

То, что передается в закрытии,graph[module].code, теперь запись./src/index.jsЭтот файл будет передан в переменную модуля в запросе и собственно найдет граф зависимостей./src/index.jsСоответствующий объект, а затем найти в коде соответствующий код, то есть следующий код, который был отформатирован мной, чтобы продемонстрировать эффект

'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

это будет выглядеть на графике./message.jsСоответствующий код ниже, но то, что мы храним на графике, это'.\\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)

Итак, относительный путь'./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, а объект экспорта по-прежнему отсутствует, поэтому мы определяем другой объект экспорта.

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Экспортируйте экспорт, чтобы следующий модуль мог получить результаты экспорта, когда модуль будет введен.Теперь процесс генерации кода завершен, и окончательный результат - большая строка, сохраните ее и запустите снова.node bundle.js | highlight

Вот я в среде 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;'
  }
})

Поместите приведенный выше код в консоль браузера и нажмите Enter для выводаsay hello

Суммировать

Это упакованное содержимое инструмента упаковки.В этот период задействованы знания узлов, для перевода ast (абстрактного синтаксического дерева) используется babel, а финальная функция generateCode включаетрекурсияа такжеЗакрытие,формальный параметра такжеАргументы, вам нужно прочитать его несколько раз, чтобы углубить ваше понимание

To Be Continued

личный блог

24 примера для начала работы и освоения "Webpack4" (1)

24 примера, чтобы начать работу и освоить "Webpack4" (2)