Начало работы с Webpack 4 и одностраничными приложениями

JavaScript HTML CSS Webpack
Начало работы с Webpack 4 и одностраничными приложениями

Webpack обновился до 4.0, а официальный сайт еще не обновил документацию. Поэтому обновите руководство, чтобы всем было проще использовать webpack 4.

webpack

напишите в начале

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

WTF

и это:

You Couldn't Handle Me

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

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

Некоторые люди могут спросить, что толку от вебпака.Если вы не можете подойти, просто поставьте меня лицом к коду и дайте мне сделать это прямо сейчас.Я сделал это снова, и результат вообще бесполезен, это все ложь. Итак, прежде чем я расскажу о вебпаке, я хотел бы рассказать об эволюции фронтенд-пакетного решения за последние несколько лет, в каких сценариях, с какими проблемами мы сталкивались и рождались инструменты для решения этих проблем. Как только вы поймете необходимость и цель, вы поймете, когда webpack может вам помочь. Надеюсь, я чувствую себя хорошо, когда закончу, и ты тоже, когда закончишь.

Поговорим о темной истории передовых упаковочных решений

В долгой истории внешнего интерфейса не было такой вещи, как упаковка. В то время страница была в основном чисто статической или серверной, без AJAX и без jQuery. В то время JavaScript был похож на игрушку.Использование, вероятно, заключалось в том, чтобы получить часы на боковой панели и использовать медиаплеер для размещения скрипта, такого как mp3.<script>Пометьте или получите файл js, чтобы процитировать его, и жизнь станет очень спокойной и счастливой.

В течение следующих нескольких лет люди начали пытаться делать больше вещей на одной странице. Показать, скрыть, переключить контейнер. Слой пули, написанный на css, карусель изображений и т. д. Однако, если страница не может запросить данные с сервера, то, что можно сделать, ограничено, и объем кода также может поддерживаться в рамках логики взаимодействия со страницей. В это время многие люди начали выходить за рамки того, что может делать страница, используя скрытые iframe и flash в качестве моста для связи с сервером, дверь в новый мир медленно открывалась, и взаимодействие данных с сервером внутри страница означала, что вещи, которые раньше требовали перехода на несколько страниц, теперь можно делать с помощью одной страницы. Но поскольку технологии iframe и flash слишком сложны и сложный, и не получил широкого распространения.

Пока Google не запустил Gmail (2004 г.), люди осознавали заброшенный интерфейс,XMLHttpRequest, также известный как AJAX, который представляет собой простой в использовании, хорошо совместимый интерфейс связи с сервером. С тех пор наша страничка заигралась всякими цветами, а во фронтенде появились всевозможные библиотеки.Prototype,Dojo,MooTools,Ext JS,jQuery... мы начали вставлять различные библиотеки и плагины на наши страницы, и наши файлы js взорвались.

Поскольку js может делать все больше и больше вещей, ссылок становится все больше и больше, а файлы становятся все больше и больше, кроме того, скорость сети в то время была всего около 2 Мбит / с, а скорость загрузки была не такой хорошей, как у Сеть 3G.Сжатие и слияние js-файлов Спрос становится все сильнее и сильнее.Конечно, есть и другие факторы, такие как запутанный код и его нелегко украсть.JSMin,YUI Compressor,Closure Compiler,UglifyJSПодождите, пока один за другим появятся инструменты сжатия и слияния файлов js. Есть инструмент сжатия, но мы должны его выполнить.Самый простой способ-создать bat-скрипт на windows и bash-скрипт на mac/linux.Какие файлы должны быть объединены вместе, а какие должны быть сжаты, запустите скрипт, когда публикация для создания сжатого файла.

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

  • Чтобы библиотеки и плагины вызывались другими, они должны найти место для регистрации, как правило, объявляя глобальную функцию или объект под окном. Трудно гарантировать, что две библиотеки, используемые в один прекрасный день, будут использовать одно и то же имя глобально, тогда возникнет конфликт.
  • Если библиотека и подключаемый модуль также зависят от других библиотек и подключаемых модулей, пользователь должен быть проинформирован о том, какие зависимые библиотеки необходимо импортировать в первую очередь.Если эти зависимые библиотеки также имеют свои собственные зависимые библиотеки, зависимые библиотеки зависимых библиотек должны быть импортированы в первую очередь, и так далее.

Именно в это время (2009 г.), с развитием технологии backend JavaScript, было предложено, чтобыCommonJSМодульная спецификация , приблизительный синтаксис: еслиa.jsполагатьсяb.jsа такжеc.js, затем вa.js, импортируйте эти файлы зависимостей:

var b = require('./b')
var c = require('./c')

затем переменнаяbа такжеcЧто бы это могло быть? Вот что экспортируют b.js и c.js Например, b.js можно экспортировать так:

exports.square = function(num) {
  return num * num
}

Затем вы можете использовать это в a.jssquareметод:

var n = b.square(2)

Если c.js зависит от d.js, экспортNumber, то можно написать:

var d = require('./d')
module.exports = d.PI // 假设 d.PI 的值是 3.14159

затем переменная в a.jscпросто цифры3.14159, конкретную спецификацию синтаксиса можно просмотреть в Node.js.Документация.

Но CommonJS не работает в браузерах. потому чтоrequire()Отдача синхронная, а это значит, что при наличии нескольких зависимостей их нужно скачивать по одной, блокируя выполнение js-скрипта. Итак, люди, определенные на основе CommonJSAsynchronous Module Definition (AMD)Спецификация (2011 г.), использующая синтаксис асинхронного обратного вызова для параллельной загрузки нескольких зависимостей, например, a.js в качестве точки входа, можно записать так:

require(['./b', './c'], function(b, c) {
  var n = b.square(2)
  console.log(c)
})

Соответствующий синтаксис экспорта также является методом асинхронного обратного вызова, напримерc.jsполагатьсяd.js, который пишется так:

define(['./d'], function(d) {
  return d.PI
})

Как видите, определение модуля заключается в использованииdefine()функция,define()а такжеrequire()Разница в том,define()Необходимо вернуть значение в callback-функции как экспортируемую вещь,require()Нет необходимости экспортировать вещи, поэтому нет необходимости возвращать значение в функции обратного вызова, и оно не может быть импортировано другими файлами в качестве зависимости, поэтому обычно используется для входных файлов, таких как загрузка на странице.a.js:

<script src="js/require.js" data-main="js/a"></script>

Вышеприведенное является базовым использованием спецификации AMD, и я не буду говорить о нем более подробно (он все равно исключен~), вы можете прочитать его, если вам интересно.здесь.

Проблема модульности js в принципе решена, а css и html не простаивают. какиеless,sass,stylusПрепроцессор css родился, говоря, что он может помочь нам упростить написание css и автоматически добавить вам префикс поставщика. html тоже появилось куча языков шаблонов в этот период, что?handlebars,ejs,jade, вы можете вставить полученные ajax данные в шаблон, а затем использовать innerHTML для отображения их на странице.

Благодаря языкам предварительной обработки и шаблонов AMD и CSS наши сценарии компиляции также состоят из сотен строк. Недостатком сценариев командной строки является то, что Windows и Mac/Linux не являются универсальными.Если существует требование кросс-платформенности, Windows должна установить инструмент командной строки, который может выполнять сценарии bash, такие как msys (последняя версияmsys2), или использовать для написания скрипты на других языках, таких как php или python, для не-full-stack фронтенд-программистов писать bash/php/python Еще очень дерганый. Поэтому нам нужен простой инструмент для упаковки, который может использовать различные инструменты компиляции для компиляции/сжатия ресурсов, таких как js, css, html и изображения. потомGruntПроизведен (2012 г.), формат конфигурационного файла — наш любимый js, и способ написания тоже очень простой.В сообществе есть множество плагинов, поддерживающих различные инструменты компиляции, lint и тестирования. Еще один упаковщик спустя больше годаgulpродился более масштабируемым и более эффективным с потоковой передачей.

Опираясь на модульное программирование AMD, реализация SPA (одностраничное приложение) стала проще и понятнее.Веб-страница больше не является традиционной страницей, похожей на текстовый документ, а представляет собой законченное приложение. Приложение SPA имеет общую страницу входа, мы обычно называем ее index.html, app.html, main.html, html<body>Вообще пусто, или только общий макет (layout), как на следующем рисунке:

layout

Макет будет заголовок, NAV, наполнением содержимого нижнего колонтитула, но основной областью является пустой контейнер. Как этот HTML-вход в наиболее важной задании - загрузить файл STARTUP SPA JS, затем управляемый JS, распределение маршрутизации на основе текущего адреса браузера, загрузите соответствующий модуль AMD, модуль AMD, а затем выполняемый, рендеринг HTML, соответствующий Указанная страница в контейнере (например, на главной рисунке). При нажатии на ссылку и другая интерактивная страница не прыгает, но нагрузка на соответствующий модуль состоит из маршрута AMD JS, затем модуль рендеринга AMD соответствует HTML в контейнер.

Хотя модули AMD упрощают реализацию SPA, существует множество мелких проблем:

  • Не все сторонние библиотеки совместимы с AMD, и вам необходимо настроить их сейчас.shim, очень хлопотно.
  • Хотя RequireJS поддерживает загрузку html как зависимость через плагины,<img>Путь является проблемой, вам нужно использовать абсолютный путь и сохранить путь упакованного изображения и путь до упаковки без изменений, или использовать язык шаблонов html дляsrcЗаписывается как переменная, генерируемая во время выполнения.
  • Динамическая загрузка CSS не поддерживается. Обходной путь – объединить и сжать все файлы CSS в один файл и одновременно загрузить его на входной html-странице.
  • Проект SPA становится все больше и больше, а упакованный js-файл приложения имеет размер в несколько МБ. несмотря на то чтоr.jsОн поддерживает упаковку подмодулей, но настройка очень хлопотная, потому что модули будут зависеть друг от друга, при настройке нужно исключить эти общие зависимости, а зависимости проверять по одной в файле.
  • Все сторонние библиотеки приходится скачивать по одной, распаковывать и помещать в определенную директорию, не говоря уже о том, как хлопотно их обновлять. Хотя его можно использоватьnpmИнструменты управления пакетами, но все пакеты npm находятся в спецификации CommonJS, используются для бэкенда Node.js, лишь частично поддерживают спецификацию AMD, и до npm 3 эти пакеты нельзя использовать, если они имеют зависимости. Позже былbowerИнструменты управления пакетами — это специализированные интерфейсные веб-хранилища, и пакеты здесь обычно поддерживаются. Спецификация АМД.
  • Синтаксис для определения и ссылки на модули в спецификации AMD слишком громоздкий. Описанный выше синтаксис AMD является лишь самым простым и общим синтаксисом. В документации API есть много вариаций, особенно когда встречается циклическая ссылка (a зависит от b, b зависит от a ), необходимо использовать другиеграмматикарешить эту проблему. И многие распространенные внешние и внутренние библиотеки в npm имеют синтаксис CommonJS. Позже многие люди начали пытаться использовать спецификацию модулей ES6, и еще одна большая проблема — как ссылаться на модули ES6.
  • Файловая структура проекта неразумна, т.к. grunt/gulp обрабатывается пакетами в соответствии с форматом файлов, поэтому js, html, css и картинки обычно размещаются в разных каталогах, поэтому файлы одного и того же модуля будут разбросаны по разным каталогам.Далее найти файлы во время разработки - хлопотно. Также очень хлопотно хотеть узнать, к какому модулю относится файл во время проверки кода.Решение состоит в том, чтобы создать папку с именем модуля в каталоге imgs и поместить в нее изображения.

На данный момент наш главный герой, webpack, здесь (2012) (здесь аплодисменты).

Подобно веб-пакету, есть такжеBrowserify. Вот краткое введение в Browserify. Цель Browserify — заставить внешний интерфейс также использовать синтаксис CommonJS.require('module')для загрузки js. Он начнется с файла записи js и поместит всеrequire()Вызванные файлы упаковываются и объединяются в один файл, что решает проблему асинхронной загрузки. затем просмотрите Что плохого в том, что я не рекомендую его использовать?Основные причины следующие:

  • Суть в том, что Browserify не поддерживает упаковку кода в несколько файлов и загрузку их по мере необходимости. Это означает, что при посещении любой страницы будут полностью загружены все файлы.
  • Загрузка Browserify других файлов, отличных от js, не идеальна, потому что в основном решаетrequire()js, другие файлы не являются той частью, о которой он заботится. Например, тег img в html-файле можно преобразовать только вData URIвместо упакованного пути.
  • Из-за вышеизложенного поддержка Browserify для загрузки файлов ресурсов не идеальна, поэтому она обычно используется вместе с gulp или grunt при упаковке, что без необходимости увеличивает сложность упаковки.
  • Browserify поддерживает только спецификацию модулей CommonJS, а не спецификации модулей AMD и ES6, что означает, что старые модули AMD и будущие модули ES6 нельзя использовать.

Исходя из вышеперечисленных пунктов, Browserify не является идеальным выбором. Так решает ли вебпак вышеописанные проблемы?Чепуха, иначе зачем его вводить. Затем в следующих главах мы используем практический способ, чтобы проиллюстрировать, как Webpack решает вышеупомянутые проблемы.

Начните с простого приложения SPA

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

Установите Node.js

webpack — это инструмент для упаковки, основанный на моем большом Node.js.Первое, что нужно сделать, это сначала установить Node.js.Портал ->.

инициализировать проект

Давайте сначала найдем случайное место и создадим папку с именемsimple, а затем построить в нем проект. Готовый продукт находится вexamples/simpleСправочник, вы можете ссылаться на него, когда делаете это. Давайте сначала посмотрим на структуру каталогов:

├── dist                      打包输出目录,只需部署这个目录到生产环境
├── package.json              项目配置信息
├── node_modules              npm 安装的依赖包都在这里面
├── src                       我们的源代码
│   ├── components            可以复用的模块放在这里面
│   ├── index.html            入口 html
│   ├── index.js              入口 js
│   ├── shared                公共函数库
│   └── views                 页面放这里
└── webpack.config.js         webpack 配置文件
скопировать код

Откройте окно командной строки,cdПерейдите в простой каталог, который вы только что создали. Затем выполните эту команду, чтобы инициализировать проект:

npm init

Командная строка попросит вас ввести некоторую информацию о конфигурации, здесь мы нажимаем, введите весь путь для создания файла конфигурации проекта по умолчаниюpackage.json.

Добавьте в проект отчеты об ошибках синтаксиса и проверку спецификации кода.

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

npm install eslint eslint-config-enough babel-eslint eslint-loader --save-dev

npm installНесколько пакетов можно установить одновременно с помощью одной команды, и пакеты разделяются пробелами. пакет будет установлен вnode_modulesв каталоге.

--save-devУстановленный пакет и номер версии будут записаны вpackage.jsonсерединаdevDependenciesВ объекте также есть--save, будет записано вdependenciesВ объекте разницу между ними можно просто понять как использование пакетов, используемых инструментами упаковки и инструментами тестирования.--save-devсохранитьdevDependencies, Такие как eslint, webpack. Пакеты, используемые js, выполняемыми в браузере, хранятся вdependencies, такие как jQuery и т. д. Так для чего они используются?

Поскольку некоторые установки пакетов npm нужно компилировать, исполняемые файлы, скомпилированные на windows/mac/linux, отличаются, то есть их нельзя использовать универсально, поэтому, когда мы отправляем код в git, мы обычно.gitignoreУказываем игнорировать каталог node_modules и файлы в нем, чтобы проекты, вытащенные другими из git, не имели каталога node_modules, тогда нам нужно запустить

npm install

это будет читатьpackage.jsonсерединаdevDependenciesа такжеdependenciesполе для загрузки соответствующей версии записанного пакета.

здесьeslint-config-enoughЭто конфигурационный файл, в котором указана спецификация кода.Чтобы сделать его эффективным, нам нужноpackage.jsonДобавьте контент в:

{
  "eslintConfig": {
    "extends": "enough",
    "env": {
      "browser": true,
      "node": true
    }
  }
}

Самая известная спецификация грамматики в отрасли — этоairbnbПроизведено, но его правила слишком жесткие, например, не разрешено использоватьfor-ofа такжеfor-inЖдать. Заинтересованные студенты могут обратиться кздесьУстановите и используйте.

babel-eslintдаeslint-config-enoughЗависимая библиотека синтаксического анализа грамматики, которая заменяет стандартную библиотеку синтаксического анализа eslint для поддержки грамматик, которые еще не были стандартизированы. Напримерimport().

eslint-loaderИспользуется для проверки кода при компиляции веб-пакета, если есть ошибка, веб-пакет сообщит об ошибке.

Установленный в проекте eslint не работает, наша IDE и редактор также должны установить плагин eslint для его поддержки.

Visual Studio Codeнеобходимо установитьРасширение ESLint

atomнеобходимо установитьlinterа такжеlinter-eslintЭти два плагина вступят в силу после перезапуска после установки.

WebStormПереключатель eslint нужно включить в настройках:

WebStorm ESLint Config

написать несколько страниц

Давайте напишем минимальное приложение SPA, чтобы описать внутреннюю работу приложения SPA. Сначала создайте файл src/index.html со следующим содержимым:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
  </head>

  <body>
  </body>
</html>

Это пустая страница, заметьте, нам не нужно писать ее самим<script src="index.js"></script>, потому что имя упакованного файла и путь могут измениться, поэтому мы используем плагин webpack, чтобы автоматически добавить его для нас.

src/index.js:

// 引入 router
import router from './router'

// 启动 router
router.start()

src/router.js:

// 引入页面文件
import foo from './views/foo'
import bar from './views/bar'

const routes = {
  '/foo': foo,
  '/bar': bar
}

// Router 类,用来控制页面根据当前 URL 切换
class Router {
  start() {
    // 点击浏览器后退 / 前进按钮时会触发 window.onpopstate 事件,我们在这时切换到相应页面
    // https://developer.mozilla.org/en-US/docs/Web/Events/popstate
    window.addEventListener('popstate', () => {
      this.load(location.pathname)
    })

    // 打开页面时加载当前页面
    this.load(location.pathname)
  }

  // 前往 path,变更地址栏 URL,并加载相应页面
  go(path) {
    // 变更地址栏 URL
    history.pushState({}, '', path)
    // 加载页面
    this.load(path)
  }

  // 加载 path 路径的页面
  load(path) {
    // 首页
    if (path === '/') path = '/foo'
    // 创建页面实例
    const view = new routes[path]()
    // 调用页面方法,把页面加载到 document.body 中
    view.mount(document.body)
  }
}

// 导出 router 实例
export default new Router()

src/views/foo/index.js:

// 引入 router
import router from '../../router'

// 引入 html 模板,会被作为字符串引入
import template from './index.html'

// 引入 css, 会生成 <style> 块插入到 <head> 头中
import './style.css'

// 导出类
export default class {
  mount(container) {
    document.title = 'foo'
    container.innerHTML = template
    container.querySelector('.foo__gobar').addEventListener('click', () => {
      // 调用 router.go 方法加载 /bar 页面
      router.go('/bar')
    })
  }
}

src/views/bar/index.js:

// 引入 router
import router from '../../router'

// 引入 html 模板,会被作为字符串引入
import template from './index.html'

// 引入 css, 会生成 <style> 块插入到 <head> 头中
import './style.css'

// 导出类
export default class {
  mount(container) {
    document.title = 'bar'
    container.innerHTML = template
    container.querySelector('.bar__gofoo').addEventListener('click', () => {
      // 调用 router.go 方法加载 /foo 页面
      router.go('/foo')
    })
  }
}

С помощью плагина webpack мы можемimporthtml, css и другие файлы форматов, текстовые файлы будут храниться как переменные и упаковываться в файлы js, другие бинарные файлы, такие как картинки, можно настроить самостоятельно, маленькие картинки какData URIУпаковываются в js файлы, большие файлы упаковываются отдельными файлами, об этом мы поговорим позже.

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

Код страницы почти готов, теперь переходим к этапу установки и настройки webpack. Сейчас мы не говорили о настройке веб-пакета, поэтому страница пока недоступна Мы увидим фактический эффект страницы после того, как конфигурация веб-пакета будет выполнена.

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

Устанавливаем в проект webpack и его плагины:

npm install webpack webpack-cli webpack-serve html-webpack-plugin html-loader css-loader style-loader file-loader url-loader --save-dev

webpackА именно основную библиотеку webpack. он предлагает многоAPI, через скрипт Node.jsrequire('webpack')способ использования веб-пакета.

webpack-cliэто инструмент командной строки для webpack. Давайте не будем писать скрипт упаковки, достаточно настроить файл конфигурации упаковки, а затем ввести в командной строкеwebpack-cli --config webpack.config.jsИспользовать webpack намного проще. До версии webpack 4 инструмент командной строки был интегрирован в пакет webpack, а начиная с версии 4.0 сам пакет webpack больше не интегрирует cli.

webpack-serveЭто сервер для разработки и отладки, предоставляемый webpack, так что вы можете использоватьhttp://127.0.0.1:8080/Такой URL открывает страницу для отладки, с ним нет необходимости настраиватьnginxДа, намного удобнее.

html-webpack-plugin, html-loader, css-loader, style-loaderКогда вы посмотрите на название, вы поймете, что это плагин для упаковки файлов html и css, здесь у вас могут возникнуть вопросы.html-webpack-pluginа такжеhtml-loaderКакая разница,css-loaderа такжеstyle-loaderВ чем разница, об этом мы поговорим позже, когда будем рассматривать файл конфигурации.

file-loaderа такжеurl-loaderЭто плагин для упаковки двоичных файлов, который также описан в главе о файлах конфигурации.

Далее, чтобы браузеры, не поддерживающие ES6 (например, IE), работали как обычно, нам нужно установитьbabel, он преобразует исходный код ES6, который мы написали, в ES5, чтобы мы писали исходный код ES6 и генерировали ES5 при упаковке.

npm install babel-core babel-preset-env babel-loader --save-dev

здесьbabel-coreКак следует из названия, это основной компилятор Babel.babel-preset-envэто файл конфигурации, мы можем использовать этот файл конфигурации для преобразованияES2015/ ES2016/ ES2017До ES5, да, не только ES6. варить идругие файлы конфигурации.

свет установленbabel-preset-env, это не вступит в силу при упаковке, вам нужноpackage.jsonПрисоединяйсяbabelКонфигурация:

{
  "babel": {
    "presets": ["env"]
  }
}

Babel читается в упаковкеpackage.jsonсерединаbabelСодержимое поля, а затем выполните соответствующее преобразование.

babel-loaderЭто плагин для веб-пакета, о нем мы поговорим в следующей главе.

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

Пакеты все упакованы, и тогда мы наконец можем перейти к делу. Давайте создадим файл конфигурации веб-пакетаwebpack.config.js, обратите внимание, что этот файл работает в node.js и поэтому не поддерживает ES6.importграмматика. Посмотрим на содержимое файла:

const { resolve } = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const history = require('connect-history-api-fallback')
const convert = require('koa-connect')

// 使用 WEBPACK_SERVE 环境变量检测当前是否是在 webpack-server 启动的开发环境中
const dev = Boolean(process.env.WEBPACK_SERVE)

module.exports = {
  /*
  webpack 执行模式
  development:开发环境,它会在配置文件中插入调试相关的选项,比如 moduleId 使用文件路径方便调试
  production:生产环境,webpack 会将代码做压缩等优化
  */
  mode: dev ? 'development' : 'production',

  /*
  配置 source map
  开发模式下使用 cheap-module-eval-source-map, 生成的 source map 能和源码每行对应,方便打断点调试
  生产模式下使用 hidden-source-map, 生成独立的 source map 文件,并且不在 js 文件中插入 source map 路径,用于在 error report 工具中查看 (比如 Sentry)
  */
  devtool: dev ? 'cheap-module-eval-source-map' : 'hidden-source-map',

  // 配置页面入口 js 文件
  entry: './src/index.js',

  // 配置打包输出相关
  output: {
    // 打包输出目录
    path: resolve(__dirname, 'dist'),

    // 入口 js 的打包输出文件名
    filename: 'index.js'
  },

  module: {
    /*
    配置各种类型文件的加载器,称之为 loader
    webpack 当遇到 import ... 时,会调用这里配置的 loader 对引用的文件进行编译
    */
    rules: [
      {
        /*
        使用 babel 编译 ES6 / ES7 / ES8 为 ES5 代码
        使用正则表达式匹配后缀名为 .js 的文件
        */
        test: /\.js$/,

        // 排除 node_modules 目录下的文件,npm 安装的包不需要编译
        exclude: /node_modules/,

        /*
        use 指定该文件的 loader, 值可以是字符串或者数组。
        这里先使用 eslint-loader 处理,返回的结果交给 babel-loader 处理。loader 的处理顺序是从最后一个到第一个。
        eslint-loader 用来检查代码,如果有错误,编译的时候会报错。
        babel-loader 用来编译 js 文件。
        */
        use: ['babel-loader', 'eslint-loader']
      },

      {
        // 匹配 html 文件
        test: /\.html$/,
        /*
        使用 html-loader, 将 html 内容存为 js 字符串,比如当遇到
        import htmlString from './template.html';
        template.html 的文件内容会被转成一个 js 字符串,合并到 js 文件里。
        */
        use: 'html-loader'
      },

      {
        // 匹配 css 文件
        test: /\.css$/,

        /*
        先使用 css-loader 处理,返回的结果交给 style-loader 处理。
        css-loader 将 css 内容存为 js 字符串,并且会把 background, @font-face 等引用的图片,
        字体文件交给指定的 loader 打包,类似上面的 html-loader, 用什么 loader 同样在 loaders 对象中定义,等会下面就会看到。
        */
        use: ['style-loader', 'css-loader']
      },

      {
        /*
        匹配各种格式的图片和字体文件
        上面 html-loader 会把 html 中 <img> 标签的图片解析出来,文件名匹配到这里的 test 的正则表达式,
        css-loader 引用的图片和字体同样会匹配到这里的 test 条件
        */
        test: /\.(png|jpg|jpeg|gif|eot|ttf|woff|woff2|svg|svgz)(\?.+)?$/,

        /*
        使用 url-loader, 它接受一个 limit 参数,单位为字节(byte)

        当文件体积小于 limit 时,url-loader 把文件转为 Data URI 的格式内联到引用的地方
        当文件大于 limit 时,url-loader 会调用 file-loader, 把文件储存到输出目录,并把引用的文件路径改写成输出后的路径

        比如 views/foo/index.html 中
        <img src="smallpic.png">
        会被编译成
        <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAA...">

        而
        <img src="largepic.png">
        会被编译成
        <img src="/f78661bef717cf2cc2c2e5158f196384.png">
        */
        use: [
          {
            loader: 'url-loader',
            options: {
              limit: 10000
            }
          }
        ]
      }
    ]
  },

  /*
  配置 webpack 插件
  plugin 和 loader 的区别是,loader 是在 import 时根据不同的文件名,匹配不同的 loader 对这个文件做处理,
  而 plugin, 关注的不是文件的格式,而是在编译的各个阶段,会触发不同的事件,让你可以干预每个编译阶段。
  */
  plugins: [
    /*
    html-webpack-plugin 用来打包入口 html 文件
    entry 配置的入口是 js 文件,webpack 以 js 文件为入口,遇到 import, 用配置的 loader 加载引入文件
    但作为浏览器打开的入口 html, 是引用入口 js 的文件,它在整个编译过程的外面,
    所以,我们需要 html-webpack-plugin 来打包作为入口的 html 文件
    */
    new HtmlWebpackPlugin({
      /*
      template 参数指定入口 html 文件路径,插件会把这个文件交给 webpack 去编译,
      webpack 按照正常流程,找到 loaders 中 test 条件匹配的 loader 来编译,那么这里 html-loader 就是匹配的 loader
      html-loader 编译后产生的字符串,会由 html-webpack-plugin 储存为 html 文件到输出目录,默认文件名为 index.html
      可以通过 filename 参数指定输出的文件名
      html-webpack-plugin 也可以不指定 template 参数,它会使用默认的 html 模板。
      */
      template: './src/index.html',

      /*
      因为和 webpack 4 的兼容性问题,chunksSortMode 参数需要设置为 none
      https://github.com/jantimon/html-webpack-plugin/issues/870
      */
      chunksSortMode: 'none'
    })
  ]
}

/*
配置开发时用的服务器,让你可以用 http://127.0.0.1:8080/ 这样的 url 打开页面来调试
并且带有热更新的功能,打代码时保存一下文件,浏览器会自动刷新。比 nginx 方便很多
如果是修改 css, 甚至不需要刷新页面,直接生效。这让像弹框这种需要点击交互后才会出来的东西调试起来方便很多。

因为 webpack-cli 无法正确识别 serve 选项,使用 webpack-cli 执行打包时会报错。
因此我们在这里判断一下,仅当使用 webpack-serve 时插入 serve 选项。
issue:https://github.com/webpack-contrib/webpack-serve/issues/19
*/
if (dev) {
  module.exports.serve = {
    // 配置监听端口,默认值 8080
    port: 8080,

    // add: 用来给服务器的 koa 实例注入 middleware 增加功能
    add: app => {
      /*
      配置 SPA 入口

      SPA 的入口是一个统一的 html 文件,比如
      http://localhost:8080/foo
      我们要返回给它
      http://localhost:8080/index.html
      这个文件
      */
      app.use(convert(history()))
    }
  }
}

иди один

Конфигурация в порядке, давайте запустим ее дальше. Сначала попробуем среду разработкиwebpack-serve:

./node_modules/.bin/webpack-serve webpack.config.js

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

Приведенная выше команда работает в системах *nix, таких как Mac/Linux, а также в средах PowerShell и bash/zsh в Windows (Windows Subsystem for Linux, Git Bash,Babun,MSYS2Ждать). Amway для Windows студентов для использованияUbuntu on Windows, что позволяет избежать многих межплатформенных проблем, таких как установка переменных среды.

Если вы используете Windows cmd.exe, выполните:

node_modules\.bin\webpack-serve webpack.config.js
скопировать код

npm установит исполняемый файл пакета в./node_modules/.bin/каталог, поэтому мы должны выполнить команду в этом каталоге.

После выполнения команды в консоли отображается:

「wdm」: Compiled successfully。
скопировать код

Это означает, что компиляция успешна, мы можем открыть его в браузереhttp://localhost:8080/Посмотрите на эффект. Если есть ошибка, то что это может быть? Пожалуйста, внимательно проверьте ~

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

Для выхода из компиляции нажмитеctrl+c.

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

./node_modules/.bin/webpack-cli

Не нужно указывать файл конфигурации, по умолчанию он читает webpack.config.js

Команда для выполнения скрипта немного громоздка, поэтому мы можем использовать npm для записи команды вpackage.jsonсередина:

{
  "scripts": {
    "dev": "webpack-serve webpack.config.js",
    "build": "webpack-cli"
  }
}

package.jsonсерединаscriptsОбъект, может использоваться для написания некоторых команд сценария, команды не нуждаются в префиксе каталога../node_modules/.bin/, npm будет автоматически искать команды в этом каталоге. Мы можем выполнить:

npm run dev

для запуска среды разработки.

воплощать в жизнь

npm run build

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

Расширенная конфигурация

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

  • Установите префикс пути URL для статических ресурсов
  • Каждая страница упакована отдельно
  • Сторонние библиотеки и бизнес-код упакованы отдельно
  • Выходной файл записи плюс хэш
  • Среда разработки закрывает performance.hints
  • Настроить фавикон
  • Среда разработки разрешает доступ с других компьютеров
  • Настройка некоторых параметров при упаковке
  • Специальные правила для webpack-serve для обработки файлов с расширениями пути
  • Вставьте переменные окружения в код
  • Упрощение путей импорта
  • Оптимизация производительности скомпилированного кода babel
  • Используйте обработку модуля ES6, которая поставляется с веб-пакетом.
  • Используйте autoprefixer для автоматического создания префиксов поставщиков css

Итак, продолжим совершенствоваться на основе приведенной выше конфигурации, только пропишем измененную часть в следующем коде. код находится вexamples/advancedсодержание.

Установите префикс пути URL для статических ресурсов

Теперь URL-адрес нашего файла ресурсов находится непосредственно в корневом каталоге, напримерhttp://127.0.0.1:8080/index.js, это не очень удобно для управления кешем и CDN, поэтому к url ​​файла ресурсов добавляем префикс, напримерhttp://127.0.0.1:8080/assets/index.js, Давайте изменим конфигурацию веб-пакета:

{
  output: {
    publicPath: '/assets/'
  }
}

webpack-serveТакже нужно изменить:

if (dev) {
  module.exports.serve = {
    port: 8080,
    host: '0.0.0.0',
    dev: {
      /*
      指定 webpack-dev-middleware 的 publicpath
      一般情况下与 output.publicPath 保持一致(除非 output.publicPath 使用的是相对路径)
      https://github.com/webpack/webpack-dev-middleware#publicpath
      */
      publicPath: '/assets/'
    },
    add: app => {
      app.use(convert(history({
        index: '/assets/' // index.html 文件在 /assets/ 路径下
      })))
    }
  }
}

Каждая страница упакована отдельно

Таким образом, браузеру нужно только загрузить код, необходимый для текущей страницы.

webpack может ссылаться на модули, загружая файлы асинхронно, мы используемasync/ awaitа такжеdynamic importреализовать:

src/router.js:

// 将 async/await 转换成 ES5 代码后需要这个运行时库来支持
import 'regenerator-runtime/runtime'

const routes = {
  // import() 返回 promise
  '/foo': () => import('./views/foo'),
  '/bar.do': () => import('./views/bar.do')
}

class Router {
  // ...

  // 加载 path 路径的页面
  // 使用 async/await 语法
  async load(path) {
    // 首页
    if (path === '/') path = '/foo'

    // 动态加载页面
    const View = (await routes[path]()).default

    // 创建页面实例
    const view = new View()

    // 调用页面方法,把页面加载到 document.body 中
    view.mount(document.body)
  }
}

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

regenerator-runtimeдаregeneratorБиблиотека времени выполнения. Babel через плагиныtransform-regeneratorиспользоватьregeneratorБудуgeneratorПосле преобразования функций и синтаксиса async/await в синтаксис ES5 для правильного выполнения требуется библиотека времени выполнения.

И потому, чтоimport()Официально еще не вошел в стандарт, нужно использоватьsyntax-dynamic-importразобрать эту грамматику. мы можем установитьbabel-preset-stage-2, который содержитimport()И другая поддержка грамматики этапа 2.

npm install regenerator-runtime babel-preset-stage-2 --save-dev

package.jsonИзмени это:

{
  "babel": {
    "presets": [
      "env",
      "stage-2"
    ]
  }
}

Затем измените конфигурацию веб-пакета:

{
  output: {
    /*
    代码中引用的文件(js、css、图片等)会根据配置合并为一个或多个包,我们称一个包为 chunk。
    每个 chunk 包含多个 modules。无论是否是 js,webpack 都将引入的文件视为一个 module。
    chunkFilename 用来配置这个 chunk 输出的文件名。

    [chunkhash]:这个 chunk 的 hash 值,文件发生变化时该值也会变。使用 [chunkhash] 作为文件名可以防止浏览器读取旧的缓存文件。

    还有一个占位符 [id],编译时每个 chunk 会有一个id。
    我们在这里不使用它,因为这个 id 是个递增的数字,增加或减少一个chunk,都可能导致其他 chunk 的 id 发生改变,导致缓存失效。
    */
    chunkFilename: '[chunkhash].js',
  }
}

Сторонние библиотеки и бизнес-код упакованы отдельно

Таким образом, при обновлении бизнес-кода можно использовать кеш браузера, и пользователю не нужно повторно скачивать не изменившуюся стороннюю библиотеку. Самым большим улучшением в Webpack 4 является автоматическое разделение чанков, если одновременно выполняются следующие условия, чанк будет разделен:

  • Новые фрагменты можно использовать повторно, или модули находятся в каталоге node_modules.
  • Новый кусок превышает 30 КБ (перед мин + GZ сжатие)
  • Количество одновременных запросов на загрузку чанков по запросу меньше или равно 5
  • Количество одновременных запросов при первоначальной загрузке страницы меньше или равно 3

В общем, вам нужно настроить только эти параметры:

{
  plugins: [
    // ...

    /*
    使用文件路径的 hash 作为 moduleId。
    虽然我们使用 [chunkhash] 作为 chunk 的输出名,但仍然不够。
    因为 chunk 内部的每个 module 都有一个 id,webpack 默认使用递增的数字作为 moduleId。
    如果引入了一个新文件或删掉一个文件,可能会导致其他文件的 moduleId 也发生改变,
    那么受影响的 module 所在的 chunk 的 [chunkhash] 就会发生改变,导致缓存失效。
    因此使用文件路径的 hash 作为 moduleId 来避免这个问题。
    */
    new webpack.HashedModuleIdsPlugin()
  ],

  optimization: {
    /*
    上面提到 chunkFilename 指定了 chunk 打包输出的名字,那么文件名存在哪里了呢?
    它就存在引用它的文件中。这意味着一个 chunk 文件名发生改变,会导致引用这个 chunk 文件也发生改变。

    runtimeChunk 设置为 true, webpack 就会把 chunk 文件名全部存到一个单独的 chunk 中,
    这样更新一个文件只会影响到它所在的 chunk 和 runtimeChunk,避免了引用这个 chunk 的文件也发生改变。
    */
    runtimeChunk: true,

    splitChunks: {
      /*
      默认 entry 的 chunk 不会被拆分
      因为我们使用了 html-webpack-plugin 来动态插入 <script> 标签,entry 被拆成多个 chunk 也能自动被插入到 html 中,
      所以我们可以配置成 all, 把 entry chunk 也拆分了
      */
      chunks: 'all'
    }
  }
}

webpack 4 поддерживает больше ручных оптимизаций, см.:gist.GitHub.com/so преследует A/1522…

Но, как говорится в документации веб-пакета, конфигурация по умолчанию достаточно оптимизирована, не оптимизируйте вслепую вручную без тестирования.

Выходной файл записи плюс хэш

Мы упоминали вышеchunkFilenameиспользовать[chunkhash]Чтобы браузер не читал неправильный кеш, запись также необходимо хэшировать. но использоватьwebpack-serveПри запуске среды разработки файл входа не[chunkhash]Да, выдает ошибку при использовании. Так что мы только выполняемwebpack-cliиспользовать, когда[chunkhash].

{
  output: {
    filename: dev ? '[name].js' : '[chunkhash].js'
  }
}

Здесь мы используем[name]Заполнитель. Прежде чем объяснять это, давайте разберемсяentryПолное определение:

{
  entry: {
    NAME: [FILE1, FILE2, ...]
  }
}

Мы можем определить несколько файлов ввода.Например, ваш проект имеет несколько файлов ввода html, и каждый html соответствует одному или нескольким файлам ввода. Затем каждая запись может состоять из нескольких модулей, которые будут выполняться последовательно. До webpack 4 это была очень полезная фича, например, вышеупомянутые сторонние библиотеки и бизнес-код упаковывались отдельно, раньше нужно было настраивать так:

{
  entry {
    main: './src/index.js',
    vendor: ['jquery', 'lodash']
  }
}

правила входа для ссылок на файлы иimportто же самое, будем искатьnode_modulesв сумке. затем объединитеCommonsChunkPluginОтделите модуль, определенный поставщиком, от бизнес-кода и упакуйте его в отдельный фрагмент. Если запись является модулем, мы можем обойтись без формы массива.

В простом проекте мы настроилиentry: './src/index.js', простейшая форма, преобразованная в полную запись:

{
  entry: {
    main: ['./src/index.js']
  }
}

webpack присвоит этой записи имя какmain.

видеть это должен знать[name]Ты имеешь ввиду? Это имя записи.

Некоторые люди могут заметить, что есть официальный сайт документа[hash]Заполнители, этот хэш представляет собой общее значение HASH, сгенерированное в процессе компиляции, а не значение Hash одного файла и любого файла в проекте, это приведет к изменению этого значения хеш-функции.[hash]Заполнители всегда есть, но мы не хотим изменять файл, чтобы изменить хеш всех выходных файлов, чтобы мы не могли воспользоваться кэшированием браузера. Поэтому это[hash]Это не имеет смысла.

Среда разработки закрывает performance.hints

Мы заметили, что при запуске среды разработки из командной строки выдается предупреждение:

WARNING in asset size limit: The following asset(s) exceed the recommended size limit (250 kB).
This can impact web performance.
скопировать код

Это означает, что рекомендуется, чтобы размер каждого выходного файла js не превышал 250 КБ. Но среда разработки обычно превышает этот размер, поскольку содержит исходную карту и код не сжат, поэтому мы можем отключить это предупреждение в среде разработки.

Добавьте в конфигурацию веб-пакета:

{
  performance: {
    hints: dev ? false : 'warning'
  }
}

Настроить фавикон

Поместите favicon.png в каталог src, затемsrc/index.htmlиз<head>Вставить в:

<link rel="icon" type="image/png" href="favicon.png">

Измените конфигурацию веб-пакета:

{
  module: {
    rules: [
      {
        test: /\.html$/,
        use: [
          {
            loader: 'html-loader',
            options: {
              /*
              html-loader 接受 attrs 参数,表示什么标签的什么属性需要调用 webpack 的 loader 进行打包。
              比如 <img> 标签的 src 属性,webpack 会把 <img> 引用的图片打包,然后 src 的属性值替换为打包后的路径。
              使用什么 loader 代码,同样是在 module.rules 定义中使用匹配的规则。

              如果 html-loader 不指定 attrs 参数,默认值是 img:src, 意味着会默认打包 <img> 标签的图片。
              这里我们加上 <link> 标签的 href 属性,用来打包入口 index.html 引入的 favicon.png 文件。
              */
              attrs: ['img:src', 'link:href']
            }
          }
        ]
      },

      {
        /*
        匹配 favicon.png
        上面的 html-loader 会把入口 index.html 引用的 favicon.png 图标文件解析出来进行打包
        打包规则就按照这里指定的 loader 执行
        */
        test: /favicon\.png$/,

        use: [
          {
            // 使用 file-loader
            loader: 'file-loader',
            options: {
              /*
              name:指定文件输出名
              [hash] 为源文件的hash值,[ext] 为后缀。
              */
              name: '[hash].[ext]'
            }
          }
        ]
      },

      // 图片文件的加载配置增加一个 exclude 参数
      {
        test: /\.(png|jpg|jpeg|gif|eot|ttf|woff|woff2|svg|svgz)(\?.+)?$/,

        // 排除 favicon.png, 因为它已经由上面的 loader 处理了。如果不排除掉,它会被这个 loader 再处理一遍
        exclude: /favicon\.png$/,

        use: [
          {
            loader: 'url-loader',
            options: {
              limit: 10000
            }
          }
        ]
      }
    ]
  }
}

На самом деле html-webpack-plugin принимаетfaviconВ параметрах можно указать путь к файлу фавикона, который будет автоматически упакован и вставлен в html файл. но у него естьbugПуть к имени файла после упаковки без хеша, даже с хешем, это тоже [хеш], а не [чанкхеш]. Изменение кода приведет к изменению фавикона Имя выходного файла пакета. Можно упомянуть проблему с favicons-webpack-plugin, но она зависит от PhantomJS, очень большая.

Среда разработки разрешает доступ с других компьютеров

const internalIp = require('internal-ip')

module.exports.serve = {
  host: '0.0.0.0',
  hot: {
    host: {
      client: internalIp.v4.sync(),
      server: '0.0.0.0'
    }
  },
  
  // ...
}

Настройка некоторых параметров при упаковке

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

Еще один момент заключается в том, что некоторые конфигурации веб-пакетов в среде разработки, тестовой среде и производственной среде различаются, напримерpublicPathВ производственной среде вы можете настроить адрес CDN.

Создаем папку в корневом каталогеconfig, который создает 3 файла конфигурации:

  • default.js: производственная среда
module.exports = {
  publicPath: 'http://cdn.example.com/assets/'
}
  • dev.js: среда разработки по умолчанию
module.exports = {
  publicPath: '/assets/',

  serve: {
    port: 8090
  }
}
  • local.js: Персональная локальная среда, измените некоторые параметры на основе dev.js.
const config = require('./dev')
config.serve.port = 8070
module.exports = config

package.jsonИсправлятьscripts:

{
  "scripts": {
    "local": "npm run webpack-serve --config=local",
    "dev": "npm run webpack-serve --config=dev",
    "webpack-serve": "webpack-serve webpack.config.js",
    "build": "webpack-cli"
  }
}

изменение конфигурации веб-пакета:

// ...
const url = require('url')

const config = require('./config/' + (process.env.npm_config_config || 'default'))

module.exports = {
  // ...
  
  output: {
    // ...
    publicPath: config.publicPath
  }
  
  // ...
}

if (dev) {
  module.exports.serve = {
    host: '0.0.0.0',
    port: config.serve.port,
    dev: {
      publicPath: config.publicPath
    },
    add: app => {
      app.use(convert(history({
        index: url.parse(config.publicPath).pathname
      })))
    }
  }
}

Ключ здесьnpm runВходящие пользовательские параметры могут быть переданы черезprocess.env.npm_config_*получать. Если какой-либо из параметров-будет преобразован в_.

Еще один момент: нам не нужно отправлять наши личные файлы конфигурации в git, поэтому мы в.gitignoreДобавить в:

config/*
!config/default.js
!config/dev.js
скопировать код

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

Некоторые ученики могли заметитьwebpack-cliв состоянии пройти--envспособ передать аргументы скрипту из командной строки, к сожалениюwebpack-cli не поддерживается.

Специальные правила для webpack-serve для обработки файлов с суффиксами

При обработке запросов с суффиксами, такими какhttp://localhost:8080/bar.do,connect-history-api-fallbackОн будет думать, что это должен быть реальный файл, и даже если файл не найден, он не вернется к index.html, а вернет 404. Но в SPA-приложении это не то, что нам нужно.

К счастью, есть вариант конфигурацииdisableDotRule: trueЭто правило можно отключить, чтобы файлы с суффиксами могли вернуться к index.html, даже если они не существуют.

module.exports.serve = {
  // ...
  add: app => {
    app.use(convert(history({
      // ...
      disableDotRule: true,
      htmlAcceptHeaders: ['text/html', 'application/xhtml+xml'] // 需要配合 disableDotRule 一起使用
    })))
  }
}

Вставьте переменные окружения в код

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

мы можем использоватьDefinePluginПлагин вставляет необходимые переменные среды в код при упаковке.

// ...
const pkgInfo = require('./package.json')

module.exports = {
  // ...
  plugins: [
    new webpack.DefinePlugin({
      DEBUG: dev,
      VERSION: JSON.stringify(pkgInfo.version),
      CONFIG: JSON.stringify(config.runtimeConfig)
    }),
    // ...
  ]
}

Принцип работы плагина DefinePlugin очень прост, если прописать в коде:

console.log(DEBUG)

Он будет делать что-то вроде этого:

'console.log(DEBUG)'.replace('DEBUG', true)

Наконец сгенерировано:

console.log(true)

Одна вещь, чтобы отметить здесь, как здесьVERSION, если мы ошибаемсяpkgInfo.versionДелатьJSON.stringify(),

console.log(VERSION)

Затем выполните операцию замены:

'console.log(VERSION)'.replace('VERSION', '1.0.0')

Наконец сгенерировано:

console.log(1.0.0)

Этот синтаксис неверен. Итак, нам нужноJSON.stringify(pkgInfo.version)превратить его в'"1.0.0"', при замене будут использоваться кавычки.

Еще один момент заключается в том, что когда webpack упаковывает и сжимает, он оптимизирует код, например:

if (DEBUG) {
  console.log('debug mode')
} else {
  console.log('production mode')
}

будет скомпилирован в:

if (false) {
  console.log('debug mode')
} else {
  console.log('production mode')
}

Затем сжатие оптимизируется до:

console.log('production mode')

Упрощение путей импорта

Когда файл a представляет файл b, путь b относится к каталогу, в котором находится файл a. Если a и b находятся в разных каталогах и глубоко спрятаны, будет проблематично написать:

import b from '../../../components/b'

Для удобства мы можем определить псевдоним пути:

resolve: {
  alias: {
    '~': resolve(__dirname, 'src')
  }
}

Таким образом, мы можемsrcпуть на основе каталога кimportдокумент:

import b from '~/components/b'

в HTML<img>Теги не могут использовать эту функцию псевдонимов, ноhtml-loaderесть одинrootпараметры, можно сделать/файлы, начинающиеся сrootРазрешение каталога.

{
  test: /\.html$/,
  use: [
    {
      loader: 'html-loader',
      options: {
        root: resolve(__dirname, 'src'),
        attrs: ['img:src', 'link:href']
      }
    }
  ]
}

Так,<img src="/favicon.png">Вы можете легко указать на файл favicon.png в каталоге src, и вам не нужно заботиться об относительном пути между текущим файлом и целевым файлом.

ПС: отладка<img>Я столкнулся с ямой при маркировке,html-loaderбудет анализировать<!-- -->Содержимое заметки, ранее написанное в заметке

<!--
大于 10kb 的图片,图片会被储存到输出目录,src 会被替换为打包后的路径
<img src="/assets/f78661bef717cf2cc2c2e5158f196384.png">
-->

потому что не добавилrootпараметры, поэтому/Имя файла в начале не будет анализироваться, добавляяrootЭто приводит к ошибке при компиляции, и файл не может быть найден. Помните об этом.

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

Код, скомпилированный Babel, как правило, приводит к снижению производительности.looseвариант, так что скомпилированный код не должен полностью соответствовать правилам ES6, упрощая скомпилированный код и повышая эффективность выполнения кода:

package.json:

{
  "babel": {
    "presets": [
      [
        "env",
        {
          "loose": true
        }
      ],
      "stage-2"
    ]
  }
}

Но при этом возникает риск совместимости, что может привести к тому, что ожидаемый результат выполнения исходного кода ES6 и фактический результат скомпилированного кода ES5 будут несогласованными. Если код не сталкивается с реальными узкими местами эффективности, официальныйне предлагаетсяиспользоватьlooseмодель.

Используйте обработку модуля ES6, которая поставляется с веб-пакетом.

В нашей текущей конфигурации babel преобразует определения модулей ES6 в определения CommonJS, но webpack может справиться с этим сам.importа такжеexport, и дескрипторы веб-пакетаimportОптимизация кода будет производиться, когда код не используется, а неиспользуемая часть кода будет удалена. Поэтому мы предоставляем его через Babelmodules: falseОпция отключает возможность конвертировать модули ES6 в модули CommonJS.

package.json:

{
  "babel": {
    "presets": [
      [
        "env",
        {
          "loose": true,
          "modules": false
        }
      ],
      "stage-2"
    ]
  }
}

Используйте autoprefixer для автоматического создания префиксов поставщиков css

Очень неприятная проблема с css заключается в том, что относительно новые свойства css должны иметь префикс в каждом браузере, мы можем использоватьautoprefixerИнструмент автоматически создает эти правила браузера, поэтому нам нужно только написать в нашем css:

:fullscreen a {
    display: flex
}

autoprefixer будет компилироваться в:

:-webkit-full-screen a {
    display: -webkit-box;
    display: flex
}
:-moz-full-screen a {
    display: flex
}
:-ms-fullscreen a {
    display: -ms-flexbox;
    display: flex
}
:fullscreen a {
    display: -webkit-box;
    display: -ms-flexbox;
    display: flex
}

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

npm install postcss-loader autoprefixer --save-dev

автопрефиксерpostcssПлагин для , поэтому нам также нужно установить веб-пакет postcssloader.

Измените правило CSS веб-пакета:

{
  test: /\.css$/,
  use: ['style-loader', 'css-loader', 'postcss-loader']
}

затем создайте файлpostcss.config.js:

module.exports = {
  plugins: [
    require('autoprefixer')()
  ]
}

Используйте веб-пакет для упаковки многостраничных приложений (многостраничное приложение).

Многостраничные сайты также могут быть упакованы с помощью webpack для использования пакетов npm,import(),code splittingи другие преимущества.

MPA означает, что вместо одной записи html и записи js каждая страница соответствует одному html и нескольким js. Затем мы можем разработать структуру проекта как:

├── dist
├── package.json
├── node_modules
├── src
│   ├── components
│   ├── shared
|   ├── favicon.png
│   └── pages                 页面放这里
|       ├── foo               编译后生成 http://localhost:8080/foo.html
|       |    ├── index.html
|       |    ├── index.js
|       |    ├── style.css
|       |    └── pic.png
|       └── bar                        http://localhost:8080/bar.html
|           ├── index.html
|           ├── index.js
|           ├── style.css
|           └── baz                    http://localhost:8080/bar/baz.html
|               ├── index.html
|               ├── index.js
|               └── style.css
└── webpack.config.js
скопировать код

Каждая страница здесьindex.htmlявляется полным<!DOCTYPE html>начать</html>конец страницы, эти файлы должны быть использованыhtml-webpack-pluginиметь дело с.index.jsэто бизнес-логика каждой страницы, так как запись js каждой страницы настроена наentryсередина. Здесь нам нужно использоватьglobБиблиотека для фильтрации этих файлов для пакетных операций. Чтобы использовать webpack 4optimization.splitChunksа такжеoptimization.runtimeChunkфункция, я написалhtml-webpack-include-sibling-chunks-pluginплагин для использования с ним. Также установите несколько плагинов для сжатия css и поместите его в<head>середина.

npm install glob html-webpack-include-sibling-chunks-plugin uglifyjs-webpack-plugin mini-css-extract-plugin optimize-css-assets-webpack-plugin --save-dev

webpack.config.jsместо нужно изменить:

// ...
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin')
const HtmlWebpackIncludeSiblingChunksPlugin = require('html-webpack-include-sibling-chunks-plugin')
const glob = require('glob')

const dev = Boolean(process.env.WEBPACK_SERVE)
const config = require('./config/' + (process.env.npm_config_config || 'default'))

const entries = glob.sync('./src/**/index.js')
const entry = {}
const htmlPlugins = []
for (const path of entries) {
  const template = path.replace('index.js', 'index.html')
  const chunkName = path.slice('./src/pages/'.length, -'/index.js'.length)
  entry[chunkName] = dev ? [path, template] : path
  htmlPlugins.push(new HtmlWebpackPlugin({
    template,
    filename: chunkName + '.html',
    chunksSortMode: 'none',
    chunks: [chunkName]
  }))
}

module.exports = {
  entry,

  output: {
    path: resolve(__dirname, 'dist'),
    // 我们不定义 publicPath,否则访问 html 时需要带上 publicPath 前缀
    filename: dev ? '[name].js' : '[chunkhash].js',
    chunkFilename: '[chunkhash].js'
  },

  optimization: {
    runtimeChunk: true,
    splitChunks: {
      chunks: 'all'
    },
    minimizer: dev ? [] : [
      new UglifyJsPlugin({
        cache: true,
        parallel: true,
        sourceMap: true
      }),
      new OptimizeCSSAssetsPlugin()
    ]
  },

  module: {
    rules: [
      // ...
      
      {
        test: /\.css$/,
        use: [dev ? 'style-loader' : MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader']
      },
      
      // ...
    ]
  },

  plugins: [
    // ...
    
    /*
    这里不使用 [chunkhash]
    因为从同一个 chunk 抽离出来的 css 共享同一个 [chunkhash]
    [contenthash] 你可以简单理解为 moduleId + content 生成的 hash
    因此一个 chunk 中的多个 module 有自己的 [contenthash]
    */
    new MiniCssExtractPlugin({
      filename: '[contenthash].css',
      chunkFilename: '[contenthash].css'
    }),

    // 必须放在html-webpack-plugin前面
    new HtmlWebpackIncludeSiblingChunksPlugin(),

    ...htmlPlugins
  ],

  // ...
}

entryа такжеhtmlPluginsОн будет сгенерирован путем обхода каталога pages, например:

entry:

{
  'bar/baz': './src/pages/bar/baz/index.js',
  bar: './src/pages/bar/index.js',
  foo: './src/pages/foo/index.js'
}

В среде разработки, чтобы иметь возможность автоматически обновлять веб-страницу после изменения html-файла, нам также необходимо добавить html-файл в запись, например:

{
  foo: ['./src/pages/foo/index.js', './src/pages/foo/index.html']
}

Таким образом, при изменении файла index.js или index.html страницы foo браузер будет обновлять страницу. Хотя добавление html в запись выглядит странно, не волнуйтесь, это не вызовет ошибки. Помните, что не следует делать этого в рабочей среде, иначе файл чанка будет содержать бесполезные фрагменты html.

htmlPlugins:

[
  new HtmlWebpackPlugin({
    template: './src/pages/bar/baz/index.html',
    filename: 'bar/baz.html',
    chunksSortMode: 'none',
    chunks: ['bar/baz']
  },

  new HtmlWebpackPlugin({
    template: './src/pages/bar/index.html',
    filename: 'bar.html',
    chunksSortMode: 'none',
    chunks: ['bar']
  },

  new HtmlWebpackPlugin({
    template: './src/pages/foo/index.html',
    filename: 'foo.html',
    chunksSortMode: 'none',
    chunks: ['foo']
  }
]

код находится вexamples/mpaсодержание.

Суммировать

С помощью этой статьи я думаю, что каждый должен научиться правильной позе открытия веб-пакета. Хотя я не упомянул, как скомпилировать с помощью веб-пакетаReactа такжеvue.js, но вы можете себе представить, что это не что иное, как установка какого-то загрузчика и плагина для работы сjsxа такжеvueФорматировать файлы, тогда сложность не в вебпаке, а в организации структуры кода. Давайте узнаем сами.

Лицензия на авторское право

知识共享许可协议
В этой работе используетсяCreative Commons Attribution — некоммерческая международная лицензия 4.0Лицензия.