Многостраничная упаковка приложения
В большинстве сценариев нашей повседневной работы мы используемwebpack
Создание традиционной одиночной страницыspa
заявление.
Так называемое одностраничное приложение означает, что упакованный код генерирует только одну копию.html
файл, основанный на интерфейсной маршрутизацииjs
Для управления рендерингом разных страниц.
Конечно, так называемое многостраничное приложение просто означает, что после упаковки создается несколько страниц.html
документ.
В этой статье мы сосредоточимся на многостраничных приложениях, а контент, затронутый в статье, — это чистая галантерея. Не будем нести чушь, статья позволит вам досконально разобраться в построении так называемого инженерного многостраничного приложения.
Конфигурацию шаблона, используемую в статье, можно посмотреть, нажав здесь.Нажмите здесь👇.
не забудьте датьstar
Ах, большие ребята (Молитесь за лицо.jpg)
Передняя часть основана на базовой конфигурации для сборки с нуля.React+TypeScript+Webpack
Объясняющая часть, если вы уже достаточно знаете об этом, вы можете сразу перейти кВ многостраничное приложениеПерейдите к настройке динамического многостраничного раздела.
Конечный результат, которого мы достигли, выглядит следующим образом:
Прости меня, я действительно неgif...
Инициализировать структуру каталогов
Давайте сначала инициализируем самый простой каталог проекта:
Сначала установимwebpack
так же какReact
:
yarn add -D webpack webpack-cli
yarn add react react-dom
webpack-cli
даwebpack
инструмент командной строки для использования в командной строкеwebpack
.
Далее давайте создадим разные каталоги страниц отдельно, предположив, что у нас есть два многостраничных приложения.
Одинeditor
страница редактора, аhome
домашняя страница.
После завершения установки позвольте мне изменить файл каталога изменений:
Конфигурация созданного проекта выглядит следующим образом, сначала поговорим об этих двух основных папках.
-
containers
Папки хранят бизнес-логику в разных проектах -
packages
Папки хранят входные файлы в разных проектах
Мы не заботимся о содержимом этих двух файлов в первую очередь и устанавливаем только самый простой каталог файлов.
настроитьreact
служба поддержки
Далее пусть наш проект сначала поддерживает самый примитивныйjsx
файл, пусть проект поддерживаетreact
изjsx
.
служба поддержкиjsx
Требует дополнительной настройкиbabel
иметь дело сjsx
файл, будетjsx
Перевод, чтобы браузер мог распознатьjs
.
Здесь нам нужно использовать следующие библиотеки:
babel-loader
@babel/core
@babel/preset-env
@babel/plugin-transform-runtime
@babel/preset-react
Давайте немного разберемся с этимbabel
роль, в частностиbabel
Я не буду вдаваться в подробности здесь.
babel-loader
Первый для нашего проектаjsx
файл нам нужно передать "переводчик" в проектjsx
файл вjs
документ,babel-loader
Здесь действует именно этот переводчик.
@babel/core
ноbabel-loader
только идентифицированыjsx
файл, необходимый для внутренней основной функции перевода@babel/core
Эта основная библиотека,@babel/core
Модуль отвечает за реализацию внутренней трансляции ядра.
@babel/preset-env
@babel/prest-env
даbabel
Некоторые пресеты в процессе перевода, он отвечает за преобразование некоторых основныхes 6+
грамматика, напримерconst/let...
Переводит в синтаксис совместимости низкого уровня, который могут распознать браузеры.
Здесь следует отметить, что
@babel/prest-ent
не для некоторыхes6+
Нет встроенной реализации некоторого высокоуровневого синтаксиса, такого как
Promise
Ждатьpolyfill
, вы можете понимать это как преобразование на уровне синтаксиса, которое не включает модули высокого уровня (polyfill
) реализация.
@babel/plugin-transform-runtime
@babel/plugin-transform-runtime
, Выше мы упомянули, что для некоторых встроенных модулей высокой версии, таких какPromise/Generate
так далее@babel/preset-env
не конвертирует, так что@babel/plugin-transform-runtime
Чтобы помочь нам добиться этого эффекта, он нам пригодится, если проектPromise
и другие модули для реализации более низкой версии браузераpolyfill
.
На самом деле с
@babel/plugin-transform-runtime
Для достижения того же эффекта вы также можете напрямую установить и импортировать@babel/polyfill
, но, напротив, этот метод не рекомендуется, у него есть недостатки, такие как загрязнение глобальной области видимости, введение слишком большого количества ссылок и повторная инъекция между модулями.
На данный момент мы уже можем реализовать эти плагиныes6+
Код компилируется в браузер, чтобы распознать совместимость с низкой версией.js
Код, но мы все еще упускаем самый важный момент.
В настоящее время эти плагины имеют дело сjs
файл, мы должны позволить ей распознать его и обработатьjsx
документ.
@babel/preset-react
На этом этапе мы представляем наш ключевой@babel/preset-react
Этот плагин вjsx
в мы используемjsx
Теги в конечном итоге компилируются в:
Заинтересованные друзья могут проверить мою предыдущую статью
React
серединаjsx
Принципиальный анализ.
В конечном итоге мы надеемся.jsx
файл преобразован вjs
файл также будетjsx
теги преобразуются вReact.createElement
форме, то нам нужно дополнительно использоватьbabel
Еще один плагин для -@babel/preset-react
.
@babel/preset-react
Представляет собой набор пресетов.Так называемые пресеты представляют собой встроенные серииbabel plugin
для преобразованияjsx
Код становится тем, что мы хотимjs
код.
babel
Требуемая конфигурация На этом мы закончили говорить о пакетах и соответствующих функциях, которые необходимо использовать, т.к.babel
Используемый принцип компиляции очень прост, поэтому нам нужно только знать, как его настроить здесь.Заинтересованные друзья могут двигатьсяbabel
Подробности смотрите на официальном сайте.
проектbabel
настроить
Далее давайте установим эти 5 плагинов, и вwebpack
настроить в:
yarn add -D @babel/core @babel/preset-env babel-loader @babel/plugin-transform-runtime @babel/preset-react
Создать базуwebpack
настроить
После того, как мы установили инструменты компиляции выше, давайте создадим базовыйwebpack.config.js
использовать его для перевода нашегоjsx
документ:
Давайте создадим его в проекте и каталогеscripts/webpack.base.js
документ.
оwebpack
Для перевода кода так называемый перевод буквальноwebpack
По умолчанию только обработка на основеjs json
Содержание.
Если мы хотимwebpack
обрабатывать нашиjsx
контент, вам нужно настроитьloader
скажи это,
"Привет,webpack
врезаться.jsx
суффиксные файлы используют этоloader
обрабатывать. "
Давайте напишем основноеbabel-loader
Конфигурация:
webpack.base.js
// scripts/webpack.base.js
const path = require('path');
module.exports = {
// 入口文件,这里之后会着重强调
entry: {
main: path.resolve(__dirname, '../src/packages/home/index.jsx'),
},
module: {
rules: [
{
test: /\.jsx?$/,
use: 'babel-loader',
},
],
},
};
В этот момент мы рассказалиwebpack
, если встретишьjsx
илиjs
код, нам нужно использоватьbabel-loader
обработаноbaebel
в проектеjs/jsx
Обработка файлов становится кодом, распознаваемым старыми браузерами.
.babelrc
Выше мы говорили оbabel-loader
Просто мост к другим плагинам, которые действительно нуждаются в транспиляции. Давайте использовать его дальше:
babel-loader
Предусмотрено два метода настройки, один из которых находится непосредственно вwebpack
прописано в конфигурационном файлеoptions
, другой официально рекомендуется строить в каталоге проекта.babelrc
конфигурация файлаbabel
.
Здесь мы используем вторую рекомендуемую конфигурацию:
// .babelrc
{
"presets": [
"@babel/preset-env",
"@babel/preset-react"
],
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"regenerator": true
}
]
]
}
packages/home
Создать файл записиindex.jsx
Далее давайтеpackages/home
Создано в каталогеhome
документы для входа в бизнес
// packages/home/index.jsx
import ReactDom from 'react-dom'
const Element = <div>hello</div>
ReactDom.render(<Element />,document.getElementById('root'))
На данный момент соответствующие базовые файлы конфигурации смоделированы, все, что нам нужно сделать, это вызватьwebpack
При упаковке нашего проекта используйте то, что мы только что написалиwebpack.base.js
этот файл конфигурации.
pacakge.json
Увеличиватьbuild
сценарий
Давайте немного изменим егоpacakge.json
:
{
"name": "pages",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"build": "webpack --config ./scripts/webpack.base.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/core": "^7.15.5",
"@babel/plugin-transform-runtime": "^7.15.0",
"@babel/preset-env": "^7.15.6",
"@babel/preset-react": "^7.14.5",
"babel-loader": "^8.2.2",
"webpack": "^5.53.0",
"webpack-cli": "^4.8.0"
},
"dependencies": {
"react": "^17.0.2",
"react-dom": "^17.0.2"
}
}
Здесь мы удаляем инициализированный
test
скрипт, добавленbuild
Заказ.
Далее бежимnpm run build
:
На этом этапе мы успешноwebpack
Идентифицироватьjsx
код и поддерживает преобразование в более высокие версииjs
Преобразовать в более низкую версиюjavascript
код вверх.
Если вы сомневаетесь, остановитесь и подумайте об общем процессе. В основном:
- Создайте
babel
Настроить переводjsx/js
содержание. - Создайте входной файл.
-
webpack
среда дляjsx/js
Использование контентаbabel-loader
передачаbabel
Настроены пресеты и плагины для перевода.
Продолжаем поддерживатьTypeScript
Бар!
настроитьTypeScript
служба поддержки
противTypeScript
На самом деле в отрасли существует два метода компиляции поддержки кода:
- непосредственно через
TypeScript
компилироватьts/tsx
код. - пройти через
babel
Переводить.
На самом деле, оба метода могут добиться компиляцииTypeScript
код становитсяJavaScript
И совместим с кодом браузера более низкой версии.
Заинтересованные друзья могут сами поискать разницу между этими двумя методами, обычно я использую его непосредственно при упаковке некоторых библиотек классов.tsc
комбинироватьtsconfig.js
компилироватьts
код.
В своей повседневной работе я лично все еще использую его в большинстве случаев.babel
Переводы часто требуются, когда дело доходит до бизнесаcss
и т.д. статические ресурсы сts
кодируйте вместе, затем используйтеbabel + webpack
На самом деле он вполне может охватывать весь процесс компиляции ресурсов за один раз.
babel
служба поддержкиTypescirpt
babel
Встроенный набор пресетов для переводаTypeScript
код --@babel/preset-typescript
.
Далее давайте использовать@babel/preset-typescript
поддержка по умолчаниюTypeScript
Грамматика.
npm install --save-dev @babel/preset-typescript
После завершения установки давайте пошагово изменим предыдущую конфигурацию:
Сначала изменим.babelrc
конфигурационный файл, пустьbabel
Перевод поддерживаетсяts
документ:
{
"presets": [
"@babel/preset-env",
"@babel/preset-react",
+ "@babel/preset-typescript"
],
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"regenerator": true
}
]
]
}
мы здесьpresets
Добавлен@babel/preset-typescrpt
по умолчанию разрешитьbabel
служба поддержкиtypescript
грамматика.
В это время нашbabel
уже идентифицируемыйTypeScript
грамматика
webpack
служба поддержкиts/tsx
документ
Не забудьте также изменить нашwebpack
серединаbabel-loader
Правила соответствия для:
// webpack.base.jf
const path = require('path');
module.exports = {
// 入口文件,这里之后会着重强调
entry: {
// 这里修改`jsx`为`tsx`
main: path.resolve(__dirname, '../src/packages/home/index.tsx'),
},
module: {
rules: [
{
// 同时认识ts jsx js tsx 文件
test: /\.(t|j)sx?$/,
use: 'babel-loader',
},
],
},
};
Здесь мы будемts,js,tsx,jsx
документы переданыbabel-loader
иметь дело с.
инициализацияtsconfig.json
Теперь наш проект уже может поддерживатьtsx
Запись файлов, но также поддерживает компиляциюts
файл более ранней версииjs
файл.
В использованииTs
Когда, обычно нам нужно настроитьtypescript
конфигурационный файл, это правильноtsconfig.json
.
Может быть, вы видели это много разtsconfig.json
Теперь давайте установим егоtypescript
И инициализируйте его~
Установить в проектTs
:
yarn add -D typescript
передачаtsc --init
инициализация командыtsconfig.json
:
npx tsc --init
о
npx
команда иnpm
отношений, о которых я подробно расскажу в другой статье. Посмотрите, для чего он здесь используется: вызов текущего проекта.node_modules/typescript/bin
Исполняемый файл выполняетсяinit
команда сделает.
Теперь наш каталог должен выглядеть так:
Хотя мы используем
babel
составлено,tsconfig.json
не вступает в силу во время компиляции. ноtsconfig.json
Конфигурация в нем очень сильно влияет на наш опыт разработки, поэтому давайте немного изменим ее.
настроитьtsconfig.json
Сначала давайте найдем соответствующийjsx
Опции:
Его роль заключается в назначенииjsx
Какой код генерируется, если говорить простыми словами, то естьjsx
Во что будет преобразован код.
Здесь мы модифицируем его наreact
.
Далее изменим его.ts
правила синтаксического анализа модуля в , измените его наnode
:
"moduleResolution": "node",
На данный момент мы сначала изменим эти две конфигурации, а затем будем постепенно настраивать последующие конфигурации в следующих пояснениях.
Порекомендуйте электронную книгу с открытым исходным кодом, большинство из которых перечислены здесь
tsconfig.json
информация о конфигурации
Обработка ошибок
Мы уже отлично поддерживаем это в проектеtypescript
, то положимpacakges/home/index.jsx
изменить наpackages/home/index.tsx
Бар.
После изменения суффикса давайте посмотрим на файл проекта, который нам нужен:
Давайте решим эти ошибки одну за другой:
Сначала мы ссылаемся на сторонний пакет вTypeScript
файл, проще говоря, он будет искать соответствующий пакетpackage.json
серединаtype
поле, чтобы найти соответствующий файл определения типа.
react
а такжеreact-dom
В коде этих двух пакетов нет соответствующего объявления типа, поэтому нам нужно установить соответствующие файлы объявления типов отдельно:
yarn add -D @types/react-dom @types/react
Большинство дополнительных пакетов определения типов, которые вы можете найти вздесьоказаться
После завершения установки давайте посмотрим на текущуюindex.tsx
документ:
На данный момент остается только одна ошибка. Рассмотрим подробнее, как найти ошибку.ts
скажи намReactDom.render
Типы параметров, переданные в метод, несовместимы. Ну, это по сути мыreact
Грамматика неверна. Модифицированный код выглядит следующим образом:
На данный момент наш проект готов поддержатьtypescript
а такжеreact
.
webpack
Настройка поддержки статических ресурсов
Зрелый проект может иметь толькоts
Как это может быть? В конце концов, как зрелому бизнесмену спастись?css
Волшебный улов 😂
Возможно, вы были в контакте сwebpack5
Предыдущая обработка статических ресурсов,file-loader,url-loader,row-loader
Этиloader
Звучит очень знакомо.
webpack
По умолчанию не поддерживается не-js
файл, так что вwebpack5
прежде чем мы прошлиloader
способ вернуть исполняемый файлjs
файл сценария, который будет обрабатывать эти внутренниеwebpack
Неизвестный файл.
существуетwebpack 5+
версия после этихloader
Функции были встроены~
Далее давайте посмотрим, как его настроить, и конкретные соответствующие функции можно посмотретьwebpack
ресурсный модуль
Обрабатывать изображения, файловые ресурсы
Тип модуля ресурсов заменяет все эти загрузчики, добавляя 4 новых типа модулей:
-
asset/resource
Отправьте отдельный файл и экспортируйте URL. ранее, используяfile-loader
выполнить. -
asset/inline
Экспортирует URI данных ресурса. ранее с помощьюurl-loader
выполнить. -
asset/source
Исходный код экспортируемого ресурса. ранее с помощьюraw-loader
выполнить. -
asset
Автоматический выбор между экспортом URI данных и отправкой отдельного файла. ранее с помощьюurl-loader
и настройте реализацию ограничения объема ресурсов.
когда вwebpack 5
использовать старыйassets loader
(Такие как file-loader
/url-loader
/raw-loader
и т. д.) и модули ресурсов, вы можете остановить обработку текущего модуля ресурсов и начать обработку снова, это может привести к дублированию ресурсов, вы можете сделать это, установив тип модуля ресурсов на'javascript/auto'
Разрешить.
О конфигурации
type:'asset'
После этого webpack автоматически загрузитresource
а такжеinline
Выберите между: файлы размером менее 8 КБ будут рассматриваться какinline
тип модуля, в противном случае рассматривается какresource
Тип модуля.
Мы можем установитьRule.parser.dataUrlCondition.maxSize
возможность изменить это условие
фактически
maxSize
эквивалентноurl-loader
серединаlimit
свойство, размер ресурса находится вmaxSize
использовать встроенныйasset/inline
обработки, использовать после превышенияresource
Экспорт ресурсов. Конечно, эта конфигурация также поддерживает экспорт реализации пользовательской конфигурации функции.
понялassets
После назначения модуля попробуем настроить его для обработки статических ресурсов:
const path = require('path');
module.exports = {
// 入口文件,这里之后会着重强调
entry: {
main: path.resolve(__dirname, '../src/packages/home/index.tsx'),
},
module: {
rules: [
{
// 同时认识ts jsx js tsx 文件
test: /\.(t|j)sx?$/,
use: 'babel-loader',
},
{
test: /\.(png|jpe?g|svg|gif)$/,
type:'asset/inline'
},
{
test: /\.(eot|ttf|woff|woff2)$/,
type: 'asset/resource',
generator: {
filename: 'fonts/[hash][ext][query]',
},
},
],
},
};
На этом шаге мы уже настроили конфигурацию файла изображения и шрифта.Далее давайте изменим структуру каталогов, чтобы проверить, действует ли наша конфигурация:
Проверьте эффект конфигурации
Исправлятьpackages
Сначала изменимpackages
,packages
Упомянутая ранее папка предназначена для хранения входного файла каждой страницы в многостраничном приложении. впусти насhome
Создайте новый в папкеapp.tsx
:
// src/packages/home/app.tsx
import React from 'react'
import ReactDom from 'react-dom'
// 这里App仅接着就会降到 它就是就是一个React FunctionComponent
import { App } from '../../containers/home/app.tsx'
ReactDom.render(<App />, document.getElementById('root'))
Исправлятьcontainers
containers
Содержимое папки — это та часть, в которой хранится различная бизнес-логика, применяемая к каждой странице.
впусти насcontainers
Чжунсинapp.tsx
В качестве последующего приложения и создайте новое в каталоге того же уровня.assets
,styles
,views
Три каталога:
-
assets
Сохраняет статические ресурсы, связанные с текущим модулем, такие как изображения, файлы шрифтов и т. д. -
styles
Есть файлы стилей, относящиеся к текущему модулю, и мы не настроили обработку связанных файлов стилей, которые будут подробно представлены позже. -
views
Храните логические разбиения соответствующих страниц в текущем модуле.
мы даемassets
создать новыйimages
папку, положитьlogo
,картина.
Исправлятьapp.tsx
Содержание следующее:
import React from 'react'
import Banner from './assets/image/banner.png'
const App: React.FC = () => {
return <div>
<p>Hello,This is pages!</p>
<img src={Banner} alt="" />
</div>
}
export {
App
}
yarn build
Наша текущая структура каталогов выглядит следующим образом:
Мы успешно построили базовую структуру каталогов, давайте запустимyarn build
.
Сначала согласноwebpack
Входной файл будет искатьpackages/home/index.tsx
,мы вindex.tsx
введены соответствующиеcontainers/app.tsx
,webpack
будет основываться на нашемimport
синтаксис для обработки зависимостей модулей для сборки модулей.
Также потому, что мыapp.tsx
фотографии представлены в
// webpack.base.js
{
test: /\.(png|jpe?g|svg|gif)$/,
type: 'asset/inline'
},
В настоящее времяtype:'assets/inline'
Обработка фотографий вступит в силу!
Давайте посмотримbuild
После файла:
asset/inline
Файлы ресурсов речи встроены вbase64
а такжеurl-loader
тот же эффект.
текущий ток
webpack
изasset/inline
Модуль конвертирует все ресурсы вbase64
Двигайтесь в пределах ряда, если вы хотите достичьurl-loader
серединаlimit
Конфигурация должна быть отключенаasset/inline
использоватьurl-loader
обрабатывать или использоватьtype:'asset'
обрабатывать.
Устранить ошибку
Осторожно, вы, возможно, обнаружили, что в настоящее время в нашем проекте есть две проблемы:
-
ts
файл дляimage
вступление кts
не могут быть правильно идентифицированы.
Способ решения этой проблемы на самом деле очень прост, мы определяемimage.d.ts
В корневом каталоге:
declare module '*.svg';
declare module '*.png';
declare module '*.jpg';
declare module '*.jpeg';
declare module '*.gif';
declare module '*.bmp';
declare module '*.tiff';
declare
для синтаксиса объявления, что означает объявитьts
Глобальные модули, чтобы мы могли нормально импортировать соответствующие ресурсы.
- мы в
index.tsx
введены соответствующиеapp.tsx
, когда присутствует суффиксts
Появится сообщение об ошибке:
Давайте решим эту проблему дальше. На самом деле проблема в имени суффикса по умолчанию при импорте файлов:
- В настоящее время
webpack
Суффикс по умолчанию не поддерживается.tsx
- а также
tsconfig.json
в имени суффикса поддержки.tsx
, поэтому оператор display вызовет ошибку.
Давайте объединим эти две конфигурации:
Объединение псевдонимов
Исправлятьwebpack
Конфигурация псевдонима
// webpack.base.js
const path = require('path');
module.exports = {
// 入口文件,这里之后会着重强调
entry: {
main: path.resolve(__dirname, '../src/packages/home/index.tsx'),
},
resolve: {
alias: {
'@src': path.resolve(__dirname, '../src'),
'@packages': path.resolve(__dirname, '../src/packages'),
'@containers': path.resolve(__dirname, '../src/containers'),
},
mainFiles: ['index', 'main'],
extensions: ['.ts', '.tsx', '.scss', 'json', '.js'],
},
module: {
rules: [
{
// 同时认识ts jsx js tsx 文件
test: /\.(t|j)sx?$/,
use: 'babel-loader',
},
{
test: /\.(png|jpe?g|svg|gif)$/,
type: 'asset/inline'
},
{
test: /\.(eot|ttf|woff|woff2)$/,
type: 'asset/resource',
generator: {
filename: 'fonts/[hash][ext][query]',
},
},
],
},
};
Здесь мы добавляемresolve
параметры, псевдонимы настроены@src
,@package
,@container
.
И когда мы не пишем суффикс файла, правила синтаксического анализа по умолчаниюextensions
правило.
Также настроеноmainFiles
, разобрать путь к папке~
Эти три конфигурации являются относительно базовыми, поэтому я не буду вдаваться в подробности. Если вы все еще не очень хорошо его понимаете, вы можете вспомнить его, когда будете использовать его все медленнее и медленнее!
Давайте попробуем изменитьindex.tsx
, используя псевдоним для импорта:
На этот раз мы обнаружили, что нет подсказки пути, это! Это действительно недопустимо!
Причина в том, что мы основываемся наtypescript
развитие, такts
файл не знает, что мы находимся вwebpack
Путь псевдонима, настроенный в .
Итак, все, что нам нужно сделать, это синхронизировать измененияtsconfig.json
,Позволятьtsconfig.json
Псевдонимы и предыдущая конфигурация также поддерживаются для достижения наилучшего опыта разработки.
Исправлятьtsconfig.json
Конфигурация псевдонима
давайте изменимtsconfig.json
,Позволятьts
Добавлена поддержка синхронизации:
// tsconfig.json
...
"baseUrl": "./",
/* Specify the base directory to resolve non-relative module names. */
"paths": {
"@src/*": ["./src/*"],
"@packages/*": ["./src/packages/*"],
"@containers/*": ["./src/containers/*"],
},
Если вы хотите настроитьpaths
Затем его необходимо настроитьbaseUrl
да, так называемыйbaseUrl
нашpaths
относится к этому пути с самого начала.
по этому мыpaths
Добавьте соответствующий путь псевдонима, чтобы завершить настройку, пустьts
Мы также можем разумно разрешать псевдонимы наших типов.
Теперь давайте посмотрим на это снова:
Подсказка пути уже может отображаться правильно, не так ли?nice
.
На этом мы подошли к концу настройки правила парсинга каталога пути/файла~
настроитьcss/sass
Затем мы добавляем несколько стилей в проект, чтобы украсить его.
На самом деле здесьReact
слишком много проектовcss
ссориться, но мы собираемсяwebpack
среда дляcss
обработанный.
Здесь я предпочитаю использовать
sass
препроцессор для демонстрации, прочееless
и т.д. одинаковые.
Таргетинг наsass
файл, то же самоеwebpack
Неизвестный файл. нам также нужноloader
иметь дело с.
используется здесьloader
следующим образом:
sass-loader
resolve-url-loader
postcss-loader
css-loader
MiniCssExtractPlugin.loader
Давайте проанализируем их один за другимloader
Соответствующая конфигурация роли:
sass-loader
Таргетинг наsass
Документ Сначала мы должны использовать егоsass
компилируется вcss
, поэтому сначала нам нужно.scss
Файл в конце скомпилирован какcss
документ.
Здесь нам нужно установить:
yarn add -D sass-loader sass
sass-loader
Требуется предварительная установкаDart Sass или Node Sass(Дополнительную информацию можно найти по этим двум ссылкам). Это может контролировать версию всех зависимостей и свободно выбирать реализацию Sass для использования.
sass-loader
похоже на то, что мы говорили раньшеbabel-loader
, который можно понимать как мост,sass
перевести наcss
Ядро состоит изnode-sass
илиdart-sass
компилировать.
resolve-url-loader
Мы уже упоминали в предыдущем шагеsass-loader
Будуsass
файл преобразован вcss
документ.
Но здесь есть фатальная проблема, которая касаетсяwebpack
правильноscss
в файле
Поскольку реализация Saass не обеспечиваетперезапись URLфункции, поэтому связанные ресурсы должны относиться к выходному файлу (ouput
) с точки зрения.
- Если сгенерированный CSS передается в
css-loader
, все правила URL должны относиться к файлу записи (например:main.scss
). - Если создается только файл CSS, он не передается в
css-loader
, тогда все URL-адреса относятся к корневому каталогу сайта.
Таким образом, дляsass
составленоcss
Путь в файле неверен, а не тот шаблон относительного пути, который нам нужен.
Для решения проблемы внедрения путей в отрасли существует множество готовых решений, например, через
- Переменная пути определяет путь импорта
- определить псевдонимы,
sass
Использовать псевдоним пути импорта в resolve-url-loader
Здесь мы используем
resolve-url-loader
для решения проблемы пути импорта файлов.
не забудь
yarn add -D resolve-url-loader
postcss-loader
Что такое PostCSS? Возможно, вы подумали бы об этом как о препроцессоре, постпроцессоре и т. д. На самом деле, это ничего. Его можно понимать как подключаемую систему.
Таргетинг наpostcss
На самом деле, я не буду подробно объяснять здесь, этоbabel
Оба два бегемота. Иметь свою независимую систему, здесь нужно четко понимать, что мы используемpostcss-loader
обработка сгенерированаcss
.
post-css
yarn add -D postcss-loader postcss
postcss-loader
loader
postcss.config.js
module.exports = {
plugins: [
require('autoprefixer'),
require('cssnano')({
preset: 'default',
}),
],
}
postcss
-
autoprefixer
css
-
cssnano
css
yarn add -D cssnano autoprefixer@latest
css-loader
css-loader
css
@import/require
yarn add -D css-loader
MiniCssExtractPlugin.loader
CSS
JS
CSS
SourceMaps
style-loader
MiniCssExtractPlugin
style-loader
style-loader
css
html
header
MiniCssExtractPlugin.loader
css
css
yarn add -D mini-css-extract-plugin
sass
sass
// webapck.base.js
const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
module.exports = {
// 入口文件,这里之后会着重强调
entry: {
main: path.resolve(__dirname, '../src/packages/home/index.tsx'),
},
resolve: {
alias: {
'@src': path.resolve(__dirname, '../src'),
'@packages': path.resolve(__dirname, '../src/packages'),
'@containers': path.resolve(__dirname, '../src/containers'),
},
mainFiles: ['index', 'main'],
extensions: ['.ts', '.tsx', '.scss', 'json', '.js'],
},
module: {
rules: [
{
test: /\.(t|j)sx?$/,
use: 'babel-loader',
},
{
test: /\.(sa|sc)ss$/,
use: [
{
loader: MiniCssExtractPlugin.loader,
},
'css-loader',
'postcss-loader',
{
loader: 'resolve-url-loader',
options: {
keepQuery: true,
},
},
{
loader: 'sass-loader',
options: {
sourceMap: true
},
},
],
},
{
test: /\.(png|jpe?g|svg|gif)$/,
type: 'asset/inline'
},
{
test: /\.(eot|ttf|woff|woff2)$/,
type: 'asset/resource',
generator: {
filename: 'fonts/[hash][ext][query]',
},
},
],
},
plugins: [
new MiniCssExtractPlugin({
filename: 'assets/[name].css',
}),
]
};
// postcss.config.js
module.exports = {
plugins: [
require('autoprefixer'),
require('cssnano')({
preset: 'default',
}),
],
}
containers/src/home/styles
sass
index.scss
:
.body {
background-color: red;
}
yarn build
dist
sass
css
scss
html
html
html
html
chunks
yarn add --dev html-webpack-plugin
public/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id='root'></div>
</body>
</html>
ReactDom.reander(...,document.getElementById('root'))
id=root
div
// webpack.base.js
const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const htmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
// 入口文件,这里之后会着重强调
entry: {
main: path.resolve(__dirname, '../src/packages/home/index.tsx'),
},
resolve: {
alias: {
'@src': path.resolve(__dirname, '../src'),
'@packages': path.resolve(__dirname, '../src/packages'),
'@containers': path.resolve(__dirname, '../src/containers'),
},
mainFiles: ['index', 'main'],
extensions: ['.ts', '.tsx', '.scss', 'json', '.js'],
},
module: {
rules: [
{
test: /\.(t|j)sx?$/,
use: 'babel-loader',
},
{
test: /\.(sa|sc)ss$/,
use: [
{
loader: MiniCssExtractPlugin.loader,
},
'css-loader',
'postcss-loader',
{
loader: 'resolve-url-loader',
options: {
keepQuery: true,
},
},
{
loader: 'sass-loader',
options: {
sourceMap: true,
},
},
],
},
{
test: /\.(png|jpe?g|svg|gif)$/,
type: 'asset/inline',
},
{
test: /\.(eot|ttf|woff|woff2)$/,
type: 'asset/resource',
generator: {
filename: 'fonts/[hash][ext][query]',
},
},
],
},
plugins: [
new MiniCssExtractPlugin({
filename: 'assets/[name].css',
}),
// 生成html名称为index.html
// 生成使用的模板为public/index.html
new htmlWebpackPlugin({
filename: 'index.html',
template: path.resolve(__dirname, '../public/index.html'),
}),
],
};
yarn build
dist
html
html
js
css
index.html
SPA
webpack
devServer
scripts
webpack.dev.js
devServer
hot:true
webpack-dev-server
// webpack.dev.js
const { merge } = require('webpack-merge');
const baseConfig = require('./webpack.base');
const path = require('path');
const devConfig = {
mode: 'development',
devServer: {
// static允许我们在DevServer下访问该目录的静态资源
// 简单理解来说 当我们启动DevServer时相当于启动了一个本地服务器
// 这个服务器会同时以static-directory目录作为跟路径启动
// 这样的话就可以访问到static/directory下的资源了
static: {
directory: path.join(__dirname, '../public'),
},
// 默认为true
hot: true,
// 是否开启代码压缩
compress: true,
// 启动的端口
port: 9000,
},
};
module.exports = merge(devConfig, baseConfig);
webpack-merge
webpack
webpack.base.js
webpack.dev.js
pacakge.json
scripts
devServer
webpack serve
...
"scripts": {
+ "dev": "webpack serve --config ./scripts/webpack.dev.js",
"build": "webpack --config ./scripts/webpack.base.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
...
yarn dev
localhost:9000
tip
devServer
scripts/utils/constant.js
// scripts/utils/constant.js
...
// 固定端口
const BASE_PROT = 9000
...
module.exports = {
MAIN_FILE,
log,
separator,
BASE_PROT
}
webpack.dev.js
:
const { merge } = require('webpack-merge')
const baseConfig = require('./webpack.base')
const portfinder = require('portfinder')
const path = require('path')
const { BASE_PROT } = require('./utils/constant')
portfinder.basePort = BASE_PROT
const devConfig = {
mode: 'development',
devServer: {
// static允许我们在DevServer下访问该目录的静态资源
// 简单理解来说 当我们启动DevServer时相当于启动了一个本地服务器
// 这个服务器会同时以static-directory目录作为跟路径启动
// 这样的话就可以访问到static/directory下的资源了
static: {
directory: path.join(__dirname, '../public'),
},
// 默认为true
hot: true,
// 是否开启代码压缩
compress: true,
// 启动的端口
port: BASE_PROT,
},
}
module.exports = async function () {
try {
// 端口被占用时候 portfinder.getPortPromise 返回一个新的端口(往上叠加)
const port = await portfinder.getPortPromise()
devConfig.devServer.port = port
return merge(devConfig, baseConfig)
} catch (e) {
throw new Error(e)
}
}
webpack
portfinder.getPortPromise()
portfinder
devConfig
yarn dev
~
js
webpack
entry
js
entry
src/packages/editor
index.tsx
webpack
entry
webpack
chunk
webapck
js
chunk
yarn build
dist
js
main.js
editor.js
js
html
js
index.html
main.js
main.html
editor.js
editor.html
html-webpack-plugin
chunks
chunks
html
chunks
js
editor.js
main.js
html
html
editor.js
main.js
htmlWebpackPlugin
editor
chunk
main
chunk
yarn build
html
js
webpack
htmlWebpackPlugin
home
editor
dev
webpack
node
pacakges
node
child_process
node
inquirer
api
nodejs
chalk
yarn add -D chalk inquirer execa
scripts
utils
utils/constant.js
constant.js
// 规定固定的入口文件名 packages/**/index.tsx
const MAIN_FILE = 'index.tsx'
const chalk = require('chalk')
// 打印时颜色
const error = chalk.bold.red
const warning = chalk.hex('#FFA500')
const success = chalk.green
const maps = {
success,
warning,
error,
}
// 因为环境变量的注入是通过字符串方式进行注入的
// 所以当 打包多个文件时 我们通过*进行连接 比如 home和editor 注入的环境变量为home*editor
// 注入多个包环境变量时的分隔符
const separator = '*'
const log = (message, types) => {
console.log(maps[types](message))
}
module.exports = {
MAIN_FILE,
log,
separator,
BASE_PORT,
}
utils/helper.js
const path = require('path')
const fs = require('fs')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { MAIN_FILE } = require('./constant')
// 获取多页面入口文件夹中的路径
const dirPath = path.resolve(__dirname, '../../src/packages')
// 用于保存入口文件的Map对象
const entry = Object.create(null)
// 读取dirPath中的文件夹个数
// 同时保存到entry中 key为文件夹名称 value为文件夹路径
fs.readdirSync(dirPath).filter(file => {
const entryPath = path.join(dirPath, file)
if (fs.statSync(entryPath)) {
entry[file] = path.join(entryPath, MAIN_FILE)
}
})
// 根据入口文件list生成对应的htmlWebpackPlugin
// 同时返回对应wepback需要的入口和htmlWebpackPlugin
const getEntryTemplate = packages => {
const entry = Object.create(null)
const htmlPlugins = []
packages.forEach(packageName => {
entry[packageName] = path.join(dirPath, packageName, MAIN_FILE)
htmlPlugins.push(
new HtmlWebpackPlugin({
template: path.resolve(__dirname, '../../public/index.html'),
filename: `${packageName}.html`,
chunks: ['manifest', 'vendors', packageName],
})
)
})
return { entry, htmlPlugins }
}
module.exports = {
entry,
getEntryTemplate,
}
helper.js
getEntryTemplate
package
webpack
entry
html-wepback-plugin
utils/dev.js
packages
-
execa
webpack
const inquirer = require('inquirer')
const execa = require('execa')
const { log, separator } = require('./constant')
const { entry } = require('./helper')
// 获取packages下的所有文件
const packagesList = [...Object.keys(entry)]
// 至少保证一个
if (!packagesList.length) {
log('不合法目录,请检查src/packages/*/main.tsx', 'warning')
return
}
// 同时添加一个全选
const allPackagesList = [...packagesList, 'all']
// 调用inquirer和用户交互
inquirer
.prompt([
{
type: 'checkbox',
message: '请选择需要启动的项目:',
name: 'devLists',
choices: allPackagesList, // 选项
// 校验最少选中一个
validate(value) {
return !value.length ? new Error('至少选择一个项目进行启动') : true
},
// 当选中all选项时候 返回所有packagesList这个数组
filter(value) {
if (value.includes('all')) {
return packagesList
}
return value
},
},
])
.then(res => {
const message = `当前选中Package: ${res.devLists.join(' , ')}`
// 控制台输入提示用户当前选中的包
log(message, 'success')
runParallel(res.devLists)
})
// 调用打包命令
async function runParallel(packages) {
// 当前所有入口文件
const message = `开始启动: ${packages.join('-')}`
log(message, 'success')
log('\nplease waiting some times...', 'success')
await build(packages)
}
// 真正打包函数
async function build(buildLists) {
// 将选中的包通过separator分割
const stringLists = buildLists.join(separator)
// 调用通过execa调用webapck命令
// 同时注意路径是相对 执行node命令的cwd的路径
// 这里我们最终会在package.json中用node来执行这个脚本
await execa('webpack', ['server', '--config', './scripts/webpack.dev.js'], {
stdio: 'inherit',
env: {
packages: stringLists,
},
})
}
dev.js
packages
execa
webpack
*
app
editor
packages
app*editor
webpack.base.js
pacakge
wepback.base.js
const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const htmlWebpackPlugin = require('html-webpack-plugin');
const { separator } = require('./utils/constant')
const { getEntryTemplate } = require('./utils/helper')
// 将packages拆分成为数组 ['editor','home']
const packages = process.env.packages.split(separator)
// 调用getEntryTemplate 获得对应的entry和htmlPlugins
const { entry, htmlPlugins } = getEntryTemplate(packages)
module.exports = {
// 动态替换entry
entry,
resolve: {
alias: {
'@src': path.resolve(__dirname, '../src'),
'@packages': path.resolve(__dirname, '../src/packages'),
'@containers': path.resolve(__dirname, '../src/containers'),
},
mainFiles: ['index', 'main'],
extensions: ['.ts', '.tsx', '.scss', 'json', '.js'],
},
module: {
rules: [
{
test: /\.(t|j)sx?$/,
use: 'babel-loader',
},
{
test: /\.(sa|sc)ss$/,
use: [
{
loader: MiniCssExtractPlugin.loader,
},
'css-loader',
'postcss-loader',
{
loader: 'resolve-url-loader',
options: {
keepQuery: true,
},
},
{
loader: 'sass-loader',
options: {
sourceMap: true,
},
},
],
},
{
test: /\.(png|jpe?g|svg|gif)$/,
type: 'asset/inline',
},
{
test: /\.(eot|ttf|woff|woff2)$/,
type: 'asset/resource',
generator: {
filename: 'fonts/[hash][ext][query]',
},
},
],
},
plugins: [
new MiniCssExtractPlugin({
filename: 'assets/[name].css',
}),
// 同时动态生成对应的htmlPlugins
...htmlPlugins
],
};
packages
getEntryTemplate
htmlWebpackPlugin
package.json
package.json
"scripts": {
- "dev": "webpack serve --config ./scripts/webpack.dev.js",
+ "dev": "node ./scripts/utils/dev.js",
"build": "webpack --config ./scripts/webpack.base.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
dev
scripts/utils/dev.js
yarn dev
:
home
home.html
home.js
localhost:9000/home.html
http://localhost:9000/editor.html
Cannot GET /editor.html
dev
production
production
production
dev
webpack
scripts
webpack.prod.js
// 这里我使用了默认的`webpack`production下的配置
// 如果你需要额外的配置,可以额外添加配置。
// 这里提供动态多页应用的流程 具体压缩/优化插件和配置 各位小哥可以去官网查看配置~
// 之后我也会在文章开头的github仓库中提供不同branch去实践最佳js代码压缩优化
const path = require('path')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const FriendlyErrorsWebpackPlugin = require('friendly-errors-webpack-plugin')
const { merge } = require('webpack-merge')
const baseConfig = require('./webpack.base')
const prodConfig = {
mode: 'production',
devtool: 'source-map',
output: {
filename: 'js/[name].js',
path: path.resolve(__dirname, '../dist'),
},
plugins: [
new CleanWebpackPlugin(),
new FriendlyErrorsWebpackPlugin()
]
}
module.exports = merge(prodConfig, baseConfig)
scripts/build.js
dev
const inquirer = require('inquirer')
const execa = require('execa')
const { log, separator } = require('./constant')
const { entry } = require('./helper')
const packagesList = [...Object.keys(entry)]
if (!packagesList.length) {
log('不合法目录,请检查src/packages/*/main.tsx', 'warning')
return
}
const allPackagesList = [...packagesList, 'all']
inquirer
.prompt([
{
type: 'checkbox',
message: '请选择需要打包的项目:',
name: 'buildLists',
choices: allPackagesList, // 选项
validate(value) {
return !value.length ? new Error('至少选择一个内容进行打包') : true
},
filter(value) {
if (value.includes('all')) {
return packagesList
}
return value
},
},
])
.then(res => {
// 拿到所有结果进行打包
const message = `当前选中Package: ${res.buildLists.join(' , ')}`
log(message, 'success')
runParallel(res.buildLists)
})
function runParallel(packages) {
const message = `开始打包: ${packages.join('-')}`
log(message, 'warning')
build(packages)
}
async function build(buildLists) {
const stringLists = buildLists.join(separator)
await execa('webpack', ['--config', './scripts/webpack.prod.js'], {
stdio: 'inherit',
env: {
packages: stringLists,
},
})
}
execa
inquirer
Eslint
& prettier
prettier
yarn add --dev --exact prettier
echo {}> .prettierrc.js
js
module.exports = {
printWidth: 100, // 代码宽度建议不超过100字符
tabWidth: 2, // tab缩进2个空格
semi: false, // 末尾分号
singleQuote: true, // 单引号
jsxSingleQuote: true, // jsx中使用单引号
trailingComma: 'es5', // 尾随逗号
arrowParens: 'avoid', // 箭头函数仅在必要时使用()
htmlWhitespaceSensitivity: 'css', // html空格敏感度
}
.prettierignore
prettier
//.prettierignore
**/*.min.js
**/*.min.css
.idea/
node_modules/
dist/
build/
husky
lint-staged
git hook
commit
lit-staged
ts
// package.json
...
"lint-staged": {
"*.{js,css,md,ts,tsx,jsx}": "prettier --write"
}
...
ESlint
Eslint
yarn add eslint --dev
eslint
npx eslint --init
eslint
prettier
eslint
yarn add -D eslint-config-prettie
eslint
eslint
prettier
eslint
// .eslint.js
module.exports = {
"env": {
"browser": true,
"es2021": true
},
"extends": [
"eslint:recommended",
"plugin:react/recommended",
"plugin:@typescript-eslint/recommended",
// 添加`prettier`拓展 用于和`prettier`冲突时覆盖`eslint`规则
"prettier"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaFeatures": {
"jsx": true
},
"ecmaVersion": 12,
"sourceType": "module"
},
"plugins": [
"react",
"@typescript-eslint"
],
"rules": {
}
};
.eslintignore
ts
*.d.ts
scripts/**
Pagescli
cli
pages
~