С сегодняшнего дня изучайте Webpack, уменьшайте зависимость от скаффолдинга (ниже)

Webpack
С сегодняшнего дня изучайте Webpack, уменьшайте зависимость от скаффолдинга (ниже)

В: Для кого эта статья?
О: Это подходит для людей, которые не знакомы с Webpack или имеют неполное представление.

В: Как устроено содержание этой статьи?
A: Сначала представьте предысторию, познакомьте с концепцией Webpack из предыстории, а затем познакомьтесь с основой, ядром и некоторыми распространенными случаями конфигурации и методами оптимизации Webpack.В Webpack действительно много плагинов и загрузчиков, и только небольшая часть из них покрыты всего 2w слов.

Вопрос: Откуда эта статья?
Ответ: Знание этой статьи происходит из платного видео (ссылка в конце статьи), статья написана мной самостоятельно, авторизована лектором и впервые опубликована в Наггетс

Предыдущий:Начните сегодня, изучите Webpack и уменьшите свою зависимость от скаффолдинга (часть 1)

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

Конфигурация ПВА

Полное имя PWAProgressive Web Application(Прогрессивная платформа приложений), она позволяет нам активно кэшировать файлы, чтобы пользователи по-прежнему могли использовать наши кэшированные файлы для открытия веб-страниц после отключения от сети, не вызывая зависания страницы. Внедрение этой технологии требует установкиworkbox-webpack-pluginплагин.

Если в вашем браузере Google Chrome не включена поддержка PWA, включите ее и выполните следующий тест.

Установить плагин

$ npm install workbox-webpack-plugin -D

Конфигурация файла webpack.config.js

// PWA只有在线上环境才有效,所以需要在webpack.prod.js文件中进行配置
const WorkboxWebpackPlugin = require('workbox-webpack-plugin');
const prodConfig = {
  // 其它配置
  plugins: [
    new MiniCssExtractPlugin({}),
    new WorkboxWebpackPlugin.GenerateSW({
      clientsClaim: true,
      skipWaiting: true
    })
  ]
}
module.exports = merge(commonConfig, prodConfig);

После того, как приведенная выше конфигурация завершена, давайте использоватьnpm run buildПакет, чтобы увидеть, какие файлы генерируются,distРезультат упаковки каталога выглядит следующим образом:

|-- dist
|   |-- index.html
|   |-- main.f28cbac9bec3756acdbe.js
|   |-- main.f28cbac9bec3756acdbe.js.map
|   |-- precache-manifest.ea54096f38009609a46058419fc7009b.js
|   |-- service-worker.js

Мы можем выделить часть блока кода, болееprecache-manifest.xxxxx.jsдокументы иservice-worker.js, именно эти два файла позволяют реализовать PWA.

Переписать index.js

Нужно определить, поддерживает ли браузер PWA, когда будет поддерживаться, будем регистрироваться и регистрироваться..jsФайл упакован для насservice-worker.jsдокумент.

console.log('hello,world');
if('serviceWorker' in navigator) {
  navigator.serviceWorker.register('/service-worker.js').then((register) => {
    console.log('注册成功');
  }).catch(error => {
    console.log('注册失败');
  })
}

Фактический эффект PWA

существуетnpm run devПосле этого используемwebpack-dev-serverЗапускается небольшой сервер, затем мы останавливаем сервер, обновляем страницу, и фактический результат PWA показан на следующем рисунке.

Переадресация запросов WebpackDevServer

В этом разделе мы изучим следующие навыки:

  • Как настроить прокси интерфейса
  • Как использовать перезапись пути интерфейса
  • Знакомство с другими распространенными конфигурациями

Предположим, у нас сейчас есть такое требование: у меня есть URL-адрес (http://www.dell-lee.com/react/api/header.json), я надеюсь, что когда я запрашиваю, запрошенный адрес/react/api/header.json, может быть что-то, что может автоматически помочь мне переслать запрос наhttp://www.dell-lee.comдоменное имя, как решить эту проблему? Вы можете использовать Webpackwebpack-dev-serverЭтот плагин для решения, который нужно настроитьproxyАтрибуты.

Как настроить прокси интерфейса

Поскольку мы собираемся делать запросы, установитеaxiosПравильнее отправить запрос, используйте следующую команду для установкиaxios:

$ npm install axios --save-dev

Поскольку наш прокси-сервер запросов можно использовать только в среде разработки, для рабочей онлайн-среды требуются другие конфигурации прокси-сервера, поэтому нам нужноwebpack.dev.jsнастройка прокси в

const devConfig = {
  // 其它配置
  devServer: {
    contentBase: './dist',
    open: false,
    port: 3000,
    hot: true,
    hotOnly: true,
    proxy: {
      '/react/api': {
        target: 'http://www.dell-lee.com'
      }
    }
  }
}

После того, как вышеуказанная конфигурация завершена, мыindex.jsимпортируется в файлaxiosмодуль, а затем перенаправить запрос.

import axios from 'axios';

axios.get('/react/api/header.json').then((res) => {
  let {data,status} = res;
  console.log(data);
})

использоватьnpm run devПосле этого мы можем увидеть в браузере, что мы успешно запросили наши данные.

Как использовать перезапись пути интерфейса

Теперь все же предположим, что есть такой сценарий:http://www.dell-lee.com/react/api/header.jsonЭтот бэкэнд-интерфейс еще не разработан, но бэкэнд говорит нам, что мы можем использовать его в первую очередь.http://www.dell-lee.com/react/api/demo.jsonЭто тестовый интерфейс, мы изменим его обратно после того, как интерфейс будет разработан. Лучший способ решить эту проблему — адрес в коде изменить нельзя, мы толькоproxyЕго можно обработать в прокси, используяpathRewriteсвойства для настройки.

const devConfig = {
  // 其它配置
  devServer: {
    contentBase: './dist',
    open: false,
    port: 3000,
    hot: true,
    hotOnly: true,
    proxy: {
      '/react/api': {
        target: 'http://www.dell-lee.com',
        pathRewrite: {
          'header.json': 'demo.json'
        }
      }
    }
  }
}

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

Последствия других распространенных конфигураций

Переслать на https:В общем, недопустимо бегать вhttpsвключено, если вы хотите переслатьhttps, вы можете использовать следующую конфигурацию

module.exports = {
  //其它配置
  devServer: {
    proxy: {
      '/react/api': {
        target: 'https://www.dell-lee.com',
        secure: false
      }
    }
  }
}

Перекрестный домен:Иногда в процессе запроса, благодаря воздействию гомологичных стратегий, существует перекрестная проблема, нам нужно обрабатывать эту ситуацию, которая может быть настроена следующим образом.

module.exports = {
  //其它配置
  devServer: {
    proxy: {
      '/react/api': {
        target: 'https://www.dell-lee.com',
        changeOrigin: true,
      }
    }
  }
}

Проксировать несколько путей к одной и той же цели:прокси несколько путей к одному и тому жеtarget, который можно настроить следующим образом

module.exports = {
  //其它配置
  devServer: {
    proxy: [{
      context: ['/vue/api', '/react/api'],
      target: 'http://www.dell-lee.com'
    }]
  }
}

Многостраничная упаковка

Сейчас популярные front-end фреймворки реализуют одностраничные ссылки (SPA), но иногда нам приходится быть совместимыми с некоторыми старыми проектами, они многостраничные, так как же настроить многостраничную упаковку? Теперь давайте подумаем о проблеме: многостраничное использование, т.е.Несколько файлов записей + несколько соответствующих файлов html, то мы можем настроитьНесколько записей + несколько конфигурацийhtml-webpack-pluginпродолжать.

Сценарий: Предположим, теперь у нас есть три таких страницы:index.html, list.html, detail.html, нам нужно настроить три файла входа и создать три новых.jsдокумент.

существуетwebpack.common.jsнастроить несколькоentryи использоватьhtml-webpack-pluginдля создания соответствующего множественного.htmlстраница.Описание параметра HtmlWebpackPlugin:

  • template: указывает, какую HTML-страницу использовать в качестве шаблона.
  • filename: Представляет имя файла сгенерированной страницы.
  • chunks: Представляет, на какие пакеты нужно ссылаться..jsдокумент
module.exports = {
  // 其它配置
  entry: {
    index: './src/index.js',
    list: './src/list.js',
    detail: './src/detail.js',
  },
  plugins: [
    new htmlWebpackPlugin({
      template: 'src/index.html',
      filename: 'index.html',
      chunks: ['index']
    }),
    new htmlWebpackPlugin({
      template: 'src/index.html',
      filename: 'list.html',
      chunks: ['list']
    }),
    new htmlWebpackPlugin({
      template: 'src/index.html',
      filename: 'detail.html',
      chunks: ['detail']
    }),
    new cleanWebpackPlugin()
  ]
}

существуетsrcСоздайте три новых каталога.jsФайлы называются:index.js,list.jsа такжеdetail.js, их коды следующие:

// index.js代码
document.getElementById('root').innerHTML = 'this is index page!'

// list.js代码
document.getElementById('root').innerHTML = 'this is list page!'

// detail.js代码
document.getElementById('root').innerHTML = 'this is detail page!'

бегатьnpm run buildУпаковать:

$ npm run build

упакованныйdistсодержание:

|-- dist
|   |-- detail.dae2986ea47c6eceecd6.js
|   |-- detail.dae2986ea47c6eceecd6.js.map
|   |-- detail.html
|   |-- index.ca8e3d1b5e23e645f832.js
|   |-- index.ca8e3d1b5e23e645f832.js.map
|   |-- index.html
|   |-- list.5f40def0946028db30ed.js
|   |-- list.5f40def0946028db30ed.js.map
|   |-- list.html

случайный выборlist.htmlЗапустив его в браузере, результат выглядит следующим образом:

Мысль: Страниц сейчас всего три, то есть надо настроить три записи + три соответствующихhtml, если у нас десять подъездов, мы тоже делаем это дублирование труда? Есть ли что-нибудь, что может помочь нам сделать это автоматически? Конечно, да!

Сначала мы определяемmakeHtmlPluginsметод, который принимает параметр конфигурации Webpackconfigs, который возвращаетpluginsмножество

const makeHtmlPlugins = function (configs) {
  const htmlPlugins = []
  Object.keys(configs.entry).forEach(key => {
    htmlPlugins.push(
      new htmlWebpackPlugin({
        template: 'src/index.html',
        filename: `${key}.html`,
        chunks: [key]
      })
    )
  })
  return htmlPlugins
}

позвонивmakeHtmlPluginsметод, который возвращаетhtmlизpluginsмассив, объедините его с исходнымpluginОбъединить, а затем скопировать вconfigs

configs.plugins = configs.plugins.concat(makeHtmlPlugins(configs));
module.exports = configs;

После того, как вышеуказанная конфигурация завершена, результат упаковки остается прежним, пожалуйста, проверьте его самостоятельно, следующееwebpack.commom.jsПолный код:

const path = require('path');
const webpack = require('webpack');
const htmlWebpackPlugin = require('html-webpack-plugin');
const cleanWebpackPlugin = require('clean-webpack-plugin');
const miniCssExtractPlugin = require('mini-css-extract-plugin');
const optimizaCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');
const configs = {
  entry: {
    index: './src/index.js',
    list: './src/list.js',
    detail: './src/detail.js'
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          { 
            loader: miniCssExtractPlugin.loader,
            options: {
              hmr: true,
              reloadAll: true
            }
          },
          'css-loader'
        ]
      },
      { 
        test: /\.js$/, 
        exclude: /node_modules/, 
        loader: [
          {
            loader: "babel-loader"
          },
          {
            loader: "imports-loader?this=>window"
          }
        ] 
      }
    ]
  },
  plugins: [
    new cleanWebpackPlugin(),
    new miniCssExtractPlugin({
      filename: '[name].css'
    }),
    new webpack.ProvidePlugin({
      '$': 'jquery',
      '_': 'lodash'
    })
  ],
  optimization: {
    splitChunks: {
      chunks: 'all'
    },
    minimizer: [
      new optimizaCssAssetsWebpackPlugin()
    ]
  },
  output: {
    filename: '[name].js',
    path: path.resolve(__dirname,'../dist')
  }
}
const makeHtmlPlugins = function (configs) {
  const htmlPlugins = []
  Object.keys(configs.entry).forEach(key => {
    htmlPlugins.push(
      new htmlWebpackPlugin({
        template: 'src/index.html',
        filename: `${key}.html`,
        chunks: [key]
      })
    )
  })
  return htmlPlugins
}
configs.plugins = configs.plugins.concat(makeHtmlPlugins(configs))
module.exports = configs

Как упаковать файл библиотеки (Library)

Почти все приведенные выше конфигурации Webpack предназначены для бизнес-кода.Если мы хотим упаковать и выпустить библиотеку для использования другими, как нам ее настроить? В следующих нескольких разделах мы поговорим о том, как упаковать файл библиотеки и сделать этот файл библиотеки пригодным для использования в различных сценариях.

Создать новый проект

шаг:

  • Создать проект библиотеки
  • использоватьnpm init -yнастроитьpackage.json
  • новыйsrcкаталог, создатьmath.jsдокумент,string.jsдокумент,index.jsдокумент
  • Создал в корневом каталогеwebpack.config.jsдокумент
  • Установитьwebpack,webpack-cli:::

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

|-- src
|   |-- index.js
|   |-- math.js
|   |-- string.js
|-- webpack.config.js
|-- package.json

Инициализировать package.json

// 初始化后,改写package.json
{
  "name": "library",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "build": "webpack"
  },
  "keywords": [],
  "author": "",
  "license": "MIT"
}

Создайте каталог src и добавьте файлы

существуетsrcновый каталогmath.js, его код представляет собой метод четырех смешанных операций, следующим образом:

export function add(a, b) {
  return a + b;
}
export function minus(a, b) {
  return a - b;
}
export function multiply(a, b) {
  return a * b;
}
export function division(a, b) {
  return a / b;
}

существуетsrcновый каталогstring.js, который имеетjoinМетоды, как показано ниже:

export function join(a, b) {
  return a + '' + b;
}

существуетsrcновый каталогindex.jsфайл, который ссылаетсяmath.jsа такжеstring.jsи экспортировать следующим образом:

import * as math from './math';
import * as string from './string';

export default { math, string };

добавить webpack.config.js

Поскольку мы собираемся упаковать файл библиотеки, поэтомуmodeНастроен только для производства (production) может быть использован.

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

const path = require('path');
module.exports = {
  mode: 'production',
  entry: './src/index.js',
  output: {
    filename: 'library.js',
    path: path.resolve(__dirname, 'dist')
  }
}

Установить вебпак

Поскольку это связано с упаковкой Webpack, нам нужно использоватьnpm instllУстановить:

$ npm install webpack webpack-cli -D

Сделать первую упаковку

использоватьnpm run buildДля первой упаковки вdistКаталог называетсяlibrary.jsфайл, если мы хотим проверить этот файл, нам нужноdistновый каталогindex.html

$ npm run build
$ cd dist
$ touch index.html

существуетindex.htmlвведен вlibrary.jsдокумент:

<script src="./library.js"></script>

На данный момент мы в основном завершили построение каталога проекта Теперь давайте рассмотрим ситуации, в которых мы можем использовать наши упакованные файлы:

  • использоватьES ModuleИмпорт синтаксиса, например.import library from 'library'
  • использоватьCommonJSИмпорт синтаксиса, например.const library = require('library')
  • использоватьAMD,CMDИмпорт синтаксиса, например.require(['library'], function() {// todo})
  • использоватьscriptВведение этикетки, например.<script src="library.js"></script>

Для приведенных выше сценариев использования мы можем настроить свойства library и libraryTarget в выходных данных (примечание: здесь library и libraryTarget не имеют ничего общего с именем нашей библиотеки library.js, первое является неотъемлемым элементом конфигурации Webpack, второе — просто наше произвольное имя берем)

const path = require('path');
module.exports = {
  mode: 'production',
  entry: './src/index.js',
  output: {
    filename: '[name].js',
    path: path.resolve(__dirname, 'dist'),
    library: 'library',
    libraryTarget: 'umd'
  }
}

Описание свойства конфигурации:

  • library: это свойство относится к глобальной переменной нашей библиотеки, подобноjqueryсередина$символ
  • libraryTarget: этот атрибут относится к схеме импорта модулей, которую должна поддерживать наша библиотека.umdПредставительская поддержкаES Module,CommomJS,AMDтак же какCMD

После завершения настройки используемnpm run buildупаковать и запустить в браузереindex.html,существуетconsoleконсольный выводlibraryЭто глобальная переменная, результат которой показан на следующем рисунке:

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

Если пользователь также введет эту стороннюю библиотеку при использовании нашего файла библиотеки, это вызовет проблему повторного обращения, то как решить эту проблему? допустимыйwebpack.config.jsконфигурация в файлеexternalsАтрибуты.

существуетstring.jsдокументjoinметод, мы используем стороннюю библиотекуlodashсередина_join()метод объединения строк.

import _ from 'lodash';
export function join(a, b) {
  return _.join([a, b], ' ');
}

После модификацииstring.jsфайл, использоватьnpm run buildупаковать, обнаружитьlodashОн напрямую упакован в наш файл библиотеки, в результате чего файл библиотеки активно раздувается до 70,8 КБ.

$ npm run build
Built at: 2019-04-05 00:47:25
     Asset      Size  Chunks             Chunk Names
library.js  70.8 KiB       0  [emitted]  main

Для решения вышеуказанных проблем мы можемwebpack.config.jsСредняя конфигурацияexternalsнедвижимость, большеexternalsиспользование, пожалуйста, нажмитеexternals

const path = require('path');
module.exports = {
  mode: 'production',
  entry: './src/index.js',
  externals: ['lodash'],
  output: {
    filename: 'library.js',
    path: path.resolve(__dirname, 'dist'),
    library: 'library',
    libraryTarget: 'umd'
  }
}

НастроеноexternalsПосле этого мы снова упаковываем его, и результат упаковки выглядит следующим образом: мы видим, что файл нашей библиотеки вернулся к исходному размеру, что доказывает, что наша конфигурация работает.

$ npm run build
Built at: 2019-04-05 00:51:22
     Asset      Size  Chunks             Chunk Names
library.js  1.63 KiB       0  [emitted]  main

Как публиковать и использовать файлы нашей библиотеки

После упаковки, как мы публикуем файлы нашей библиотеки, следующееШаги для публикации:

  • регистрnpmучетная запись
  • Исправлятьpackage.jsonЗапись файла изменена на:"main": "./dist/library.js"
  • бегатьnpm adduserДобавить имя учетной записи
  • бегатьnpm publishкоманда для публикации
  • бегатьnpm install xxxустановить

Чтобы поддерживать чистоту репозитория npm, мы фактически не запускали команду npm publish, потому что наша библиотека бессмысленна, а ее публикация — это мусорный код, поэтому, пожалуйста, попробуйте опубликовать ее самостоятельно. Кроме того, имя вашего собственного пакета не может совпадать с именем существующего пакета в репозитории npm, поэтому вам нужно присвоить атрибуту name специальное имя в package.json, например "name": "why- библиотека-2019"

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

вместе сTypeScriptнепрерывное развитие, считаю, что будущее использованиеTypeScriptписать код JS станет мейнстримом, поэтому как настроить поддержку в WebpackTypeScriptШерстяная ткань? может быть установленts-loaderа такжеtypescriptДля решения этой проблемы.

Создайте новый проект webpack-typescript

Создайте новый проект и назовите егоwebpack-typescript, и действуйте следующим образом:

  • использоватьnpm init -yинициализацияpackage.jsonфайл и добавитьbuildКоманда упаковки Webpack
  • новыйwebpack.config.jsфайл и выполните простую настройку, напримерentry,outputЖдать
  • новыйsrcкаталог и вsrcновый каталогindex.tsдокумент
  • новыйtsconfig.jsonфайл и сделать некоторые настройки
  • Установитьwebpackа такжеwebpack-cli
  • Установитьts-loaderа такжеtypescript

После выполнения вышеуказанных действий каталог проекта будет выглядеть следующим образом:

|-- src
|   |-- index.ts
|-- tsconfig.json
|-- webpack.config.js
|-- package.json

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

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

Далее нам нужноwebpack.config.jsСделайте некоторую настройку:

const path = require('path');
module.exports = {
  mode: 'production',
  module: {
    rules: [
      {
        test: /\.(ts|tsx)?$/,
        use: {
          loader: 'ts-loader'
        }
      }
    ]
  },
  entry: {
    main: './src/index.ts'
  },
  output: {
    filename: '[name].js',
    path: path.resolve(__dirname, 'dist')
  }
}

существуетtsconfig.jsonосуществляется внутриtypescriptсоответствующая конфигурация, описание элементов конфигурации выглядит следующим образом

  • module: означает, что мы используемES6модуль
  • target: означает, что мы конвертируем вES5код
  • allowJs: позвольте нам.tsчерез файлimportГрамматика знакомит с другими.jsдокумент
{
  "compilerOptions": {
    "module": "ES6",
    "target": "ES5",
    "allowJs": true
  }
}

существуетsrc/index.tsзапись в документеTypeScriptкод, как показано ниже

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

let greeter = new Greeter('why');
console.log(greeter.greet());

пакетный тест

  • бегатьnpm run buildупаковать
  • в созданииdistкаталог, создайте новыйindex.html, и представьте упакованныйmain.jsдокумент
  • запустить в браузереindex.html

Использовать файлы определения типов из других модулей

Если мы хотим использовать библиотеку lodash, мы должны установить соответствующий файл определения типа в формате @types/xxx.

УстановитьlodashсоответствующийtypescriptТип файла:

$ npm install lodash @types/lodash -D

После установки мыindex.tsцитируется вlodash, и используйте метод внутри:

import * as _ from 'lodash'

class Greeter {
  greeting: string
  constructor(message: string) {
    this.greeting = message;
  }
  greet() {
    return _.join(['hello', this.greeting], '**');
  }
}

let greeter = new Greeter('why');
console.log(greeter.greet());

пакетный тест

использоватьnpm run build, Запуск в браузереindex.html, результат следующий:

Оптимизация производительности веб-пакета

Анализ упаковки

Перед выполнением оптимизации производительности Webpack, если мы знаем, насколько велик каждый из наших связанных файлов и каково время упаковки, нам очень полезно выполнить оптимизацию производительности.Здесь мы используемwebpack-bundle-analyzerчтобы помочь нам решить эту проблему.

Сначала вам нужно установить плагин с помощью следующей команды:

$ npm install webpack-bundle-analyzer --save-dev

После установки нам нужноwebpack.prod.jsВнесите небольшое изменение в файл:

const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
const prodConfig = {
  // 其它配置项
  mode: 'production',
  plugins: [
    new BundleAnalyzerPlugin()
  ]
}

После настройки запускаемnpm run buildкоманда для просмотра результатов анализа упаковки. Следующие результаты упаковки приведены только для справки:

Сузить поиск файлов

Сначала нам нужно понять параметр конфигурации Webpack (Resolve) роль: она сообщает Webpack, как искать файлы, а также имеет несколько свойств, которые нам нужно понять:

  • extensions: он сообщает Webpack, как искать модули, когда мы импортируем модуль без написания суффикса модуля.
  • mainFields: он сообщает Webpack, как искать модуль, когда мы импортируем модуль, но не пишем конкретное имя модуля.
  • alias: Когда у нас есть сторонние библиотеки или модули, на которые нужно ссылаться, мы можем напрямую импортировать их, настроив псевдонимы..min.jsфайл, который можно разобрать непосредственно в библиотеке
  • разноеinclude,exclude,testВзаимодействовать с загрузчиком для ограничения диапазона поиска файлов

параметр расширения

Как сказано выше,extensionsОн сообщает Webpack, как искать модули, когда мы импортируем модуль, но не записываем суффикс модуля. Такая ситуация очень распространена в нашей разработке, ситуация может быть следующей:

// 书写了模块后缀
import main from 'main.js'

// 没有书写模块后缀
import main from 'main'

Как и выше, мы не пишемmain.jsиз.jsсуффикс, потому что Webpack поможет нам найти некоторые файлы по умолчанию, и мы также можем настроить нашу собственную конфигурацию суффикса файла:

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

module.exports = {
  // 其它配置
  resolve: {
    extensions: ['.js', '.json', '.vue']
  }
}

Если мы настроим его, как указано выше, мы можем написать это в коде так:

// 省略 .vue文件扩展
import BaseHeader from '@/components/base-header';

// 省略 .json文件扩展
import CityJson from '@/static/city';

параметр mainFields

mainFieldsОсновной сценарий применения параметров заключается в том, что мы можем искать по Webpack, не прописывая конкретное имя модуля, возможна следующая ситуация:

// 省略具体模块名称
import BaseHeader from '@components/base-header/';

// 以上相当于这一段代码
import BaseHeader from '@components/base-header/index.vue';
// 或者这一段
import BaseHeader from '@components/base-header/main.vue';

Мы также можем настроить свои собственныеmainFieldsпараметр:

Как и в случае с параметром extensions, мы также не рекомендуем настраивать слишком много значений mainFields по вышеуказанным причинам.

module.exports = {
  // 其它配置
  resolve: {
    extensions: ['.js', '.json', '.vue'],
    mainFields: ['main', 'index']
  }
}

псевдоним параметра

aliasПараметр больше похож на алиас.Если у вас модуль с глубокой директорией и длинным именем файла, полезно настроить алиас для удобства, для огромной сторонней библиотеки импортируйте напрямую.min.jsа не отnode_modulesТакже отличное решение для внедрения в , возможна следующая ситуация:

Модули, настроенные через псевдонимы, повлияют на Tree Shaking. Рекомендуется использовать только библиотеки с сильной целостностью. Например, библиотеки lodash не рекомендуется импортировать через псевдонимы, потому что lodash больше подходит для использования Tree Shaking.

// 没有配置别名之前
import main from 'src/a/b/c/main.js';
import React from 'react';

// 配置别名之后
import main from 'main.js';
import React from 'react';
// 别名配置
const path = require('path');
module.exports = {
  // 其它配置
  resolve: {
    extensions: ['.js', '.json', '.vue'],
    mainFields: ['main', 'index'],
    alias: {
      main: path.resolve(__dirname, 'src/a/b/c'),
      react: path.resolve(__dirname, './node_modules/react/dist/react.min.js')
    }
  }
}

Tree Shaking удаляет лишний код

Tree ShakingНастройка Мы уже говорили об этом выше, настроитьTree ShakingТоже очень просто.

module.exports = {
  // 其它配置
  optimization: {
    usedExports: true
  }
}

если ты правTree ShakingЯ все еще не понимаю, пожалуйста, нажмитеTree ShakingПодробнее.

DllPlugin уменьшает количество компиляций сторонних библиотек

Для некоторых фиксированных сторонних библиотек, поскольку они фиксированные, каждый раз, когда мы их упаковываем, Webpack будет анализировать их код и упаковывать его. Так есть ли способ, давайте упакуем его только один раз, а результаты первого анализа будем использовать непосредственно для последующих упаковок. Ответ, конечно, да, мы можем использовать встроенный Webpack.DllPluginЧтобы решить эту проблему, решение этой проблемы можно разделить на следующие шаги:

  • Упакуйте сторонние библиотеки отдельно в однуxxx.dll.jsв файле
  • существуетindex.htmlиспользуется вxxx.dll.jsдокумент
  • Результаты анализа упаковки сгенерированных сторонних библиотек хранятся вxxx.manifest.jsonв файле
  • когдаnpm run buildПри импорте результатов анализа уже упакованной сторонней библиотеки
  • оптимизация

Упакуйте сторонние библиотеки отдельно

Чтобы упаковать сторонние библиотеки отдельно, нам необходимо выполнить следующие шаги:

  • Генерируется в корневом каталогеdllпапка
  • существуетbuildсоздать каталогwebpack.dll.jsконфигурационный файл и настройте его.
  • существуетpackage.jsonфайл, настроитьbuild:dllЗаказ
  • использоватьnpm run build:dllупаковать

генерироватьdllпапка:

$ mkdir dll

существуетbuildсоздается в папкеwebpack.dll.js:

$ cd build
$ touch webpack.dll.js

После создания необходимо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]'
  }
}

Наконец нужноpackage.jsonДобавьте в файл новую команду упаковки:

{
  // 其它配置
  "scripts": {
    "dev": "webpack-dev-server --config ./build/webpack.dev.js",
    "build": "webpack --config ./build/webpack.prod.js",
    "build:dll": "webpack --config ./build/webpack.dll.js"
  }
}

использоватьnpm run build:dllРезультаты упаковки, ваши результаты упаковки будут выглядеть так:

|-- build
|   |-- webpack.common.js
|   |-- webpack.dev.js
|   |-- webpack.dll.js
|   |-- webpack.prod.js
|-- dll
|   |-- vendors.dll.js
|-- src
|   |-- index.html
|   |-- index.js
|-- package.json

Цитироватьxxx.dll.jsдокумент

В предыдущем разделе мы успешно получилиxxx.dll.jsфайл, то какindex.htmlимпортировать этот файл? Ответ - установитьadd-asset-html-webpack-pluginПлагин:

$ npm install add-asset-html-webpack-plugin -D

существуетwebpack.common.jsиспользуется вadd-asset-html-webpack-pluginПлагин:

const addAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin');
const configs = {
  // 其它配置
  plugins: [
    new addAssetHtmlWebpackPlugin({
      filepath: path.resolve(__dirname, '../dll/vendors.dll.js')
    })
  ]
}
module.exports = configs;

Мы предоставляем доступ к сторонней библиотеке по всему мируvendorsпеременная, введенная сейчасxxx.dll.jsРезультат файла выглядит следующим образом:

Создание упакованных файлов анализа

существуетwebpack.dll.jsс помощью встроенного WebpackDllPluginПлагин для анализа упаковки:

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]',
      path: path.resolve(__dirname, '../dll/[name].manifest.json')
    })
  ]
}

Ссылка на упакованный файл анализа

существуетwebpack.common.jsс помощью встроенного WebpackDllReferencePluginПлагин для ссылки на файлы анализа пакетов:

const htmlWebpackPlugin = require('html-webpack-plugin');
const cleanWebpackPlugin = require('clean-webpack-plugin');
const addAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin');
const webpack = require('webpack');
const path = require('path');
module.exports = {
  // 其它配置
  plugins: [
    new cleanWebpackPlugin(),
    new htmlWebpackPlugin({
      template: 'src/index.html'
    }),
    new addAssetHtmlWebpackPlugin({
      filepath: path.resolve(__dirname, '../dll/vendors.dll.js')
    }),
    new webpack.DllReferencePlugin({
      manifest: path.resolve(__dirname, '../dll/vendors.manifest.json')
    })
  ]
}

оптимизация

Теперь мы думаем о проблеме, которую мы сейчас ставимlodashа такжеjqueryвсе упакованоvendorsфайл, то что делать, если мы хотим разделить, и как настроить и импортировать после разделения? Возможный результат разделения выглядит следующим образом:

const path = require('path');
const webpack = require('webpack');
module.exports = {
  mode: 'production',
  entry: {
    vendors: ['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')
    })
  ]
}

Согласно приведенным выше результатам разделения, нам нужноwebpack.common.jsСоздайте следующую эталонную конфигурацию в:

const htmlWebpackPlugin = require('html-webpack-plugin');
const cleanWebpackPlugin = require('clean-webpack-plugin');
const addAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin');
const path = require('path');
const configs = {
  // ... 其他配置
  plugins: [
    new cleanWebpackPlugin(),
    new htmlWebpackPlugin({
      template: 'src/index.html'
    }),
    new addAssetHtmlWebpackPlugin({
      filepath: path.resolve(__dirname, '../dll/vendors.dll.js')
    }),
     new addAssetHtmlWebpackPlugin({
      filepath: path.resolve(__dirname, '../dll/jquery.dll.js')
    }),
    new webpack.DllReferencePlugin({
      manifest: path.resolve(__dirname, '../dll/vendors.manifest.json')
    }),
    new webpack.DllReferencePlugin({
      manifest: path.resolve(__dirname, '../dll/jquery.manifest.json')
    })
  ]
}
module.exports = configs;

Мы можем обнаружить, что по мере того, как мы внедряем все больше и больше сторонних модулей, мы постоянно модифицируем файл конфигурации Webpack. Для этой задачи мы можем использоватьNodeосновной модульfsанализироватьdllФайлы под папкой импортируются динамически, по этой идее создаем новую.makePluginsметод, который возвращает Webpackpluginsмножество:

const makePlugins = function() {
  const plugins = [
    new cleanWebpackPlugin(),
    new htmlWebpackPlugin({
      template: 'src/index.html'
    }),
  ];

  // 动态分析文件
  const files = fs.readdirSync(path.resolve(__dirname, '../dll'));
  files.forEach(file => {
    // 如果是xxx.dll.js文件
    if(/.*\.dll.js/.test(file)) {
      plugins.push(
        new addAssetHtmlWebpackPlugin({
          filepath: path.resolve(__dirname, '../dll', file)
        })
      )
    }
    // 如果是xxx.manifest.json文件
    if(/.*\.manifest.json/.test(file)) {
      plugins.push(
        new webpack.DllReferencePlugin({
          manifest: path.resolve(__dirname, '../dll', file)
        })
      )
    }
  })
  return plugins;
}
configs.plugins = makePlugins(configs);
module.exports = configs;

использоватьnpm run build:dllУпакуйте сторонние библиотеки, а затем используйтеnpm run buildУпаковка, результаты упаковки следующие:

В этом тесте время первой упаковки составляет 1100 мс+, а последующая упаковка стабильно составляет 800 мс+, что указывает на то, что наша оптимизация производительности Webpack вступила в силу.

|-- build
|   |-- webpack.common.js
|   |-- webpack.dev.js
|   |-- webpack.dll.js
|   |-- webpack.prod.js
|-- dist
|   |-- index.html
|   |-- jquery.dll.js
|   |-- main.1158fa9f961c50aaea21.js
|   |-- main.1158fa9f961c50aaea21.js.map
|-- dll
|   |-- jquery.dll.js
|   |-- jquery.manifest.json
|   |-- vendors.dll.js
|   |-- vendors.manifest.json
|-- src
|   |-- index.html
|   |-- index.js
|-- package.json
|-- postcss.config.js

резюме: Оптимизация производительности Webpack — это долгосрочная тема. Эта глава — лишь небольшой пример. В следующем блоге будет более подробная интерпретация Webpack, так что следите за обновлениями (установите флажок).

Напишите свой загрузчик

В нашем процессе использования Webpack мы используем многоloader, то теloaderОткуда это? Можем ли мы написать свой собственныйloaderа потом использовать? Ответ, конечно, да, Webpack предоставляет нам некоторыеloaderAPI, через который мы можем написать свой собственныйloaderи использовать.

Как написать и использовать свой загрузчик

Сцены: нам нужно поставить.jsфайл, все вхожденияWebpack is good!, изменился наWebpack is very good!. На самом деле нам нужно написать свой собственныйloader, поэтому у нас есть следующие шаги, чтобы справиться с:

  • новыйwebpack-loaderпроект
  • использоватьnpm init -yгенерация командpackage.jsonдокумент
  • Создайтеwebpack.config.jsдокумент
  • Создайтеsrcкаталог и вsrcновый каталогindex.js
  • Создайтеloadersкаталог и вloaderновый каталогreplaceLoader.js
  • Установитьwebpack,webpack-cli

Новый каталог проекта после вышеуказанных шагов выглядит следующим образом:

|-- loaders
|   | -- replaceLoader.js
|-- src
|   | -- index.js
|-- webpack.config.js
|-- package.json

Сначала вам нужноwebpack.config.jsДобавьте следующий код в:

const path = require('path');
module.exports = {
  mode: 'development',
  entry: './src/index.js',
  module: {
    rules: [
      {
        test: /\.js$/,
        use: [path.resolve(__dirname, './loaders/replaceLoader.js')]
      }
    ]
  },
  output: {
    filename: '[name].js',
    path: path.resolve(__dirname, 'dist')
  }
}

затем вpackage.jsonдобавление файлаbuildКоманда упаковки:

// 其它配置
"scripts": {
  "build": "webpack"
}

следующий вsrc/index.jsДобавьте строку кода в файл: Этот файл использует простейший пример и просто печатает предложение.

console.log('Webpack is good!');

Наконец вloader/replaceLoader.jsнаписать свой собственныйloaderКод в файле:

  • записыватьloaderчас,module.exports— это фиксированный способ записи, и это может быть только обычная функция, а не стрелочная функция (потому что она должна бытьthisуказывает на себя)
  • sourceявляется исходным содержимым упакованного файла
const loaderUtils = require('loader-utils');
module.exports = function(source) {
  return source.replace('good', 'very good');
}

используйте нашloader: использовать нашloader, тебе следуетmodulesкитайский языкloader,resolveLoaderОн говорит Webpack использоватьloaderКогда, в какой каталог следует перейти, по умолчаниюnode_modules, после этой конфигурации нам не нужно заполнять его путь, потому что он автоматически перейдет кloadersНайдите ниже папку.

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

Наконец мы бежимnpm run build, в сгенерированномdistоткрыть каталогmain.jsфайл, вы можете видеть, что содержимое файла было успешно заменено, что указывает на то, что нашloaderОн был успешно использован.

/***/ "./src/index.js":
/*!**********************!*\
  !*** ./src/index.js ***!
  \**********************/
/*! no static exports found */
/***/ (function(module, exports) {

eval("console.log('Webpack is very good!');\n\n//# sourceURL=webpack:///./src/index.js?");

/***/ })

/******/ });

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

вопрос:

  • Как мы можем вернуть несколько значений?
  • Как мы передаем параметры нашему загрузчику?

Как вернуть несколько значений

API Webpack позволяет нам использоватьcallback(error, result, sourceMap?, meta?)Возвращает несколько значений, имеет четыре параметра:

  • Error || Null: тип ошибки, ошибка не переданаnull
  • result: преобразованный результат
  • sourceMap: необязательный параметр, обрабатывающий проанализированныйsourceMap
  • meta: необязательный параметр, метаинформация

Возвращает несколько значений, которые могут быть следующими:

// 第三,第四个参数是可选的。
this.callback(null, result);

Как передать параметры

мы знаем, что с помощьюloaderможно записать в следующем виде:

// options里面可以传递一些参数
{
  test: /\.js$/,
  use: [{
    loader: 'replaceLoader',
    options: {
      word: 'very good'
    }
  }]
}

повторное использованиеoptionsПосле передачи параметров мы можем использовать официально предоставленныйloader-utilsполучитьoptionsпараметры, которые можно записать следующим образом:

const loaderUtils = require('loader-utils');
module.exports = function(source) {
  var options = loaderUtils.getOptions(this);
  return source.replace('good', options.word)
}

Как написать асинхронный код в Loader

В приведенных выше примерах мы все использовали синхронный код, поэтому, если у нас есть сцена, которая должна быть асинхронной, как ее реализовать? Мы могли бы также сделать такое предположение, сначала написавsetTimeout:

const loaderUtils = require('loader-utils');
module.exports = function(source) {
  var options = loaderUtils.getOptions(this);
  setTimeout(() => {
    var result = source.replace('World', options.name);
    return this.callback(null, result);
  }, 0);
}

если ты побежишьnpm run buildДля упаковки будет сообщено об ошибке.Решение: используйтеthis.async()Active ID имеет асинхронный код:

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

На данный момент мы освоили, как писать, как ссылаться, как передавать параметры и как писать асинхронный код, в следующем разделе мы научимся писать свой собственныйplugin.

Напишите свой собственный плагин

а такжеloaderТочно так же в процессе использования Webpack мы также часто используемplugin, то мы научимся писать свои собственныеpluginочень нужно. Сценарий: написание собственногоpluginСцена после упаковкиdistсоздать каталогcopyright.txtдокумент

основы плагина

pluginОсновы рассказывают, как написать свой собственныйpluginи как использовать, и создавать свои собственныеloaderТочно так же нам нужно создать следующую структуру каталогов проекта:

|-- plugins
|   -- copyWebpackPlugin.js
|-- src
|   -- index.js
|-- webpack.config.js
|-- package.json

copyWebpackPlugins.jsкод в: использоватьnpm run buildПри упаковке мы увидим вывод консолиhello, my pluginЭтот проход.

Плагин отличается от загрузчика Плагин требует от нас предоставить класс, что объясняет, почему мы должны выполнять новую операцию при использовании плагина.

class copyWebpackPlugin {
  constructor() {
    console.log('hello, my plugin');
  }
  apply(compiler) {

  }
}
module.exports = copyWebpackPlugin;

webpack.config.jsкод в:

const path = require('path');
// 引用自己的插件
const copyWebpackPlugin = require('./plugins/copyWebpackPlugin.js');
module.exports = {
  mode: 'development',
  entry: './src/index.js',
  output: {
    filename: '[name].js',
    path: path.resolve(__dirname, 'dist')
  },
  plugins: [
    // new自己的插件
    new copyWebpackPlugin()
  ]
}

Как передать параметры

используя другиеpluginПри подключении нам часто нужно передать некоторые параметры, так как же мы передаем параметры в наши собственные плагины? Где это принято?
По сути, передача параметров плагина аналогична передаче параметров других плагинов, и объект передается в конструкторе.Передача параметров плагина выглядит следующим образом:

const path = require('path');
const copyWebpackPlugin = require('./plugins/copyWebpackPlugin.js');
module.exports = {
  mode: 'development',
  entry: './src/index.js',
  output: {
    filename: '[name].js',
    path: path.resolve(__dirname, 'dist')
  },
  plugins: [
    // 向我们的插件传递参数
    new copyWebpackPlugin({
      name: 'why'
    })
  ]
}

существуетpluginв вызове конструктора: используйтеnpm run buildДля упаковки мы можем распечатать значения параметров, которые мы передали в консолиwhy

class copyWebpackPlugin {
  constructor(options) {
    console.log(options.name);
  }
  apply(compiler) {

  }
}
module.exports = copyWebpackPlugin;

Как написать и использовать свой собственный плагин

  • applyФункция — это функция, которую необходимо выполнить при вызове нашего плагина.
  • applyпараметр, относящийся к экземпляру Webpack
  • compilation.assetsИнформация об упакованном файле

Теперь у нас есть такое требование: использовать собственный плагин для генерации пакета в каталоге пакетов.copyright.txtФайл с авторскими правами, так как написать такой плагин? Сначала нам нужно знатьpluginФункция ловушки, в соответствии с нашими правилами, вызывается функцией ловушки:emit, который используется следующим образом:

class CopyWebpackPlugin {
  constructor() {
  }
  apply(compiler) {
    compiler.hooks.emit.tapAsync('CopyWebpackPlugin', (compilation, cb) => {
      var copyrightText = 'copyright by why';
      compilation.assets['copyright.txt'] = {
        source: function() {
          return copyrightText
        },
        size: function() {
          return copyrightText.length;
        }
      }
      cb();
    })
  }
}
module.exports = CopyWebpackPlugin;

использоватьnpm run buildПосле присвоения имени пакету мы видимdistкаталог, мы создали нашcopyright.txtдокумент.

|-- dist
|   |-- copyright.txt
|   |-- main.js
|-- plugins
|   |-- copyWebpackPlugin.js
|-- src
|   |-- index.js
|-- webpack.config.js
|-- package.json

мы открытыcopyright.txtфайл со следующим содержимым:

copyright by why

Этот блог размещен на MOOC VideoОт основ до реального боя, я покажу вам, как освоить новую версию Webpack 4.0.Прочитайте и систематизируйте, пожалуйста, поддержите подлинную версию при просмотре видео.