Болезненная загрузка веб-пакета по требованию

внешний интерфейс JavaScript React.js Webpack

Q1: Что такое загрузка по требованию?

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

Итак, первый вопрос, как разобрать js?

Q2: Как его удалить?

1. Как он выглядел до того, как был разделен?

Приходите на демо, посмотрим, как это выглядит до разделения: a.js:

import b from './b.js';
console.log("this is a.js")
const btn = document.querySelector("#btn");
btn.onclick = ()=>{
  b();
}

b.js:

export default ()=>{
  console.log("this is b");
}

html:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <div id="btn">btn</div>
  <script src="./dist/main.js"></script>
</body>
</html>

webpack.config.js

module.exports = {
  entry:'./a.js',
  output:{
    filename:'[name].js'
  }
}
  1. a.js ссылается на b.js
  2. Пакет webpack упаковывает b и a вместе и выводит файл main.js по умолчанию.
  3. html ссылается на упакованный main.js Результат выглядит следующим образом:

image.png | left | 315x215


2. Начинайте!

шаг 1: изменить webpack.config.js

module.exports = {
  entry:'./a.js',
  output:{
    filename:'[name].js',
    chunkFilename:'[name].js'// 设置按需加载后的chunk名字
  }
}

Вот предложение, chunkFilename просто, функция chunkFilename — это элемент конфигурации, используемый для именования разделенных фрагментов. ок, запусти вебпак

image.png | left | 296x138

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


Шаг 2: Изменить a.js

// import b from './b.js';
console.log("this is a.js")
const btn = document.querySelector("#btn");
btn.onclick = ()=>{
    import('./b').then(function(module){
      const b = module.default;
      b();
    })
}
  1. Используйте синтаксис импорта по требованию es6
  2. Выполнить возвращенный результат после обещания В этот момент снова запустите webpack:

image.png | left | 348x154

Выходной файл становится двое, один main.js и один 1.js Это 1.js сумасшедший ...

image.png | left | 668x75

Посмотрите на исходный код, можно увидеть, на самом деле, это наше b.js

В заключение :

  • Настройка вывода в webpack не решает, разбивать код или нет
  • Определяющим фактором разделения кода является синтаксис импорта.
  • Когда webpack сканирует код с синтаксисом импорта, он решает выполнить разделенный код.

Шаг 3: Как его использовать?

image.png | left | 441x230

Что ж, я успешно сообщил об ошибке... у меня болит мозг Ошибка анализа:

  • Файл загружается по запросу, чтобы найти /1.js
  • Но результат нашей упаковки находится в каталоге dist, в корневом каталоге его естественно найти невозможно

Шаг 4. Настройте базовый путь общего пути.

Эта конфигурация помогает указать базовый путь для всех ресурсов в проекте. Это называется公共路径(publicPath).Изменить webpack.config.js

module.exports = {
  entry:'./a.js',
  output:{
    filename:'[name].js',
    chunkFilename:'[name].js',// 设置按需加载后的chunk名字
    publicPath:'dist/' // 设置基础路径
  }
}

шаг 5: проверьте результат

image.png | left | 333x347


  • до щелчка
  • Упоминается только main.js

image.png | left | 413x703


  • После нажатия
  • 1.js загружен
  • И выполнить код js в 1.js
  • Вывод консоли это b.js
  • хорошо, проверка прошла успешно

Шаг 6: Заполните яму

Предыдущий 1.js не читается, и трудно выяснить, есть ли проблемы.Webpack предоставляет способ определить имена фрагментов по запросу, изменить a.js:

// import b from './b.js';
console.log("this is a.js")
const btn = document.querySelector("#btn");
btn.onclick = ()=>{
  import(/* webpackChunkName: "b" */ './b').then(function(module){
    const b = module.default;
    b();
  })
}

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

image.png | left | 474x208

Выводится B.js, и тест возвращается один раз:

image.png | left | 259x432


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

Q3: Можно ли выполнить горячее обновление после загрузки по требованию?

1, сначала запустите интеграцию webpack-dev-server

Сначала установите webpack-dev-server и настройте сценарии npm.

{
  "devDependencies": {
    "webpack-dev-server": "^3.1.9"
  },
  "scripts": {
    "start:dev": "webpack-dev-server"
  },
  "dependencies": {
    "webpack": "^4.20.2",
    "webpack-cli": "^3.1.2"
  }
}

Изменить webpack.config.js

var path = require('path');
module.exports = {
  entry:'./a.js',
  mode:'development',
  output:{
    filename:'[name].js',
    chunkFilename:'[name].js',// 设置按需加载后的chunk名字
    publicPath:'dist/'
  },
  devServer: {
    contentBase: './',
    compress: true,
    port: 9000
  }
}
  • На этот раз он больше не выполняется через команду webpack.
  • Вместо этого он выполняется через командную строку npm run start:dev.
  • webpack-dev-server прочитает конфигурацию devServer в webpack.config.js
  • хорошо, devServer был интегрирован

2, подбегаю и вижу

Изменить webpack.config.js

var path = require('path');
var webpack = require('webpack');
module.exports = {
  entry:'./a.js',
  mode:'development',
  output:{
    filename:'[name].js',
    chunkFilename:'[name].js',// 设置按需加载后的chunk名字
    publicPath:'dist/'
  },
  devServer: {
    contentBase: './',
    compress: true,
    port: 9000,
    hot: true, // 开启热更新
  },
  plugins: [ // 开始热更新
      new webpack.NamedModulesPlugin(),
      new webpack.HotModuleReplacementPlugin()
  ],
}

Всего 3 фразы:

  • горячий оператор в devServer
  • Два встроенных плагина webpack в plugins После того, как эти два плагина включены, он все равно не работает, и файл входа нужно изменить.
// import b from './b.js';
console.log("this is a.js")
const btn = document.querySelector("#btn");
btn.onclick = ()=>{
  import(/* webpackChunkName: "b" */ './b').then(function(module){
    const b = module.default;
    b();
  })
}

if (module.hot) {// 开启热替换
     module.hot.accept()
}

хорошо, это так просто, горячее обновление + загрузка по требованию будут работать вместе.


Q4: интеграция с реактивным маршрутизатором, загрузка по требованию

документация по реактивному маршрутизатору

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

Шаг 1: Добавьте загрузчик babel

Изменить webpack.config.js

var path = require('path');
var webpack = require('webpack');
module.exports = {
  entry:'./a.js',
  mode:'development',
  output:{
    filename:'[name].js',
    chunkFilename:'[name].js',// 设置按需加载后的chunk名字
    publicPath:'dist/'
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /(node_modules|bower_components)/,
        use: {
          loader: 'babel-loader',
        }
      }
    ]
  },
  devServer: {
    contentBase: './',
    compress: true,
    port: 9000,
    hot: true,
  },
  plugins: [
      new webpack.NamedModulesPlugin(),
      new webpack.HotModuleReplacementPlugin()
  ],
}

Новое добавление выше - добавить загрузчик babel


шаг 2: добавьте .babelrc

{
  "presets": ["@babel/preset-react","@babel/preset-env"]
}

шаг 3: написать jsx

Изменить a.js

import React,{Component} from 'react';
import ReactDom from 'react-dom';
import B from './b.js';
export default class A extends Component{
  render(){
    return <div>
      this is A
      <B />
    </div>
  }
}
ReactDom.render(<A/>,document.querySelector("#btn"))
if (module.hot) {
     module.hot.accept()
}

Изменить b.js

import React,{Component} from 'react';
export default class B extends Component{
  render(){
    return <div>this is B</div>
  }
}

есть тест:

  • реакция запущена
  • Горячее обновление все еще работает

Шаг 4: Интеграцияreact-loadable

Загрузка React on-demand развивалась несколькими способами, последним из которых является использование компонента react-loadable.Официальный также рекомендует использовать эту библиотеку для достижения, в настоящее время эта библиотека имеет 1w+star

Изменить a.js

import React,{Component} from 'react';
import { BrowserRouter as Router, Route, Switch,Link } from 'react-router-dom';
import ReactDom from 'react-dom';
import Loadable from 'react-loadable';

const Loading = () => <div>Loading...</div>;

const B = Loadable({
  loader: () => import('./b.js'),
  loading: Loading,
})
const C = Loadable({
  loader: () => import('./C.js'),
  loading: Loading,
})
export default class A extends Component{
  render(){
    return <div>
      <Router>
        <div>
          <Route path="/B" component={B}/>
          <Route path="/C" component={C}/>
          <Link to="/B">to B</Link><br/>
          <Link to="/C">to C</Link>
        </div>
      </Router>
    </div>
  }
}
ReactDom.render(<A/>,document.querySelector("#btn"))
if (module.hot) {
     module.hot.accept()
}

  • Синтаксис импорта, используемый в loadable, представляет собой функцию динамической загрузки, которую ECMA будет поддерживать в будущем.
  • Loadable очень прост, вам нужно только обернуть компоненты, которые необходимо загрузить, в соответствии с указанным синтаксисом.

image.png | left | 408x470

Нажмите, чтобы перейти к C

image.png | left | 413x511

Вы можете видеть, что 1.js загружен, что означает, что асинхронная загрузка завершена успешно. Но теперь есть проблема: обновляйтесь по пути /C, будет ситуация, когда маршрут не может попасть


Шаг 5: запустите экспресс для проверки

var express = require('express')
var app = express()
app.use(express.static('dist'))


app.get('*', function (req, res) {
  res.send(`<!DOCTYPE html>
  <html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
  </head>
  <body>
    <div id="btn">btn</div>
    <script src="./main.js"></script>
  </body>
  </html>`)
})
app.listen(5000);

Создайте простое экспресс-приложение:

  • проверено
  • Также выполняет загрузку по требованию

Шаг 6: Вложенные маршруты загружаются по запросу

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

import React,{Component} from 'react';
import { BrowserRouter as Router, Route, Switch,Link } from 'react-router-dom';
import ReactDom from 'react-dom';
import Loadable from 'react-loadable';

const Loading = (props) => {
  return <div>Loading...</div>
};
const B = Loadable({
  loader: () => import('./b.js'),
  loading: Loading,
})
const C = Loadable({
  loader: () => import('./c.js'),
  loading: Loading,
})
export default class A extends Component{
  render(){
    return <div>
      <Router>
        <div>
          <Route path="/B" component={B}/>
          <Route path="/C" component={C}/>
          <Link to="/B">to B</Link><br/>
          <Link to="/C">to C</Link>
        </div>
      </Router>
    </div>
  }
}
ReactDom.render(<A/>,document.querySelector("#btn"))
if (module.hot) {
     module.hot.accept()
}

Изменить c.js

import React,{Component} from 'react';
import { Route,Link} from 'react-router-dom';
import Loadable from 'react-loadable';

const Loading = (props) => {
  return <div>Loadingc...</div>
};

const D = Loadable({
  loader: () => import('./d.js'),
  loading: Loading,
})
export default class C extends Component{
  render(){
    return <div>
      this is C
      <Route path="/C/D" component={D}/>
      <Link to="/C/D">to D</Link>
    </div>
  }
}

  • Входной файл вводит две динамические маршруты B и C
  • Вложенная маршрутизация C.JS / C / D
  • Компонент D по требованию используется в маршруте /C/D.

Шаг 7: Проверка вложенных маршрутов

Вход в порядке

image.png | left | 303x152


Нажмите, чтобы перейти к динамической загрузке C без проблем

image.png | left | 328x177


Нажмите, чтобы прыгнуть D

image.png | left | 747x228


Вы можете видеть, что возникло исключение, когда ресурс ./d.js был динамически импортирован, а путь /C был добавлен необъяснимым образом.


шаг 8: чертpublicPath

Некоторое время я гадал здесь, и я проверил много контента, наконец, я понял, что должна быть проблема с настройкой publicPath, перепроверил настройки и изменил webpack.config.js.

var path = require('path');
var webpack = require('webpack');
module.exports = {
  entry:'./a.js',
  mode:'development',
  output:{
    path:path.resolve(__dirname, 'dist'),
    filename:'[name].js',
    chunkFilename:'[name].js',// 设置按需加载后的chunk名字
    publicPath:'/dist/'
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /(node_modules|bower_components)/,
        use: {
          loader: 'babel-loader',
        }
      }
    ]
  },
  devServer: {
    contentBase: './',
    compress: true,
    port: 9000,
    hot: true,
  },
  plugins: [
      new webpack.NamedModulesPlugin(),
      new webpack.HotModuleReplacementPlugin()
  ],
}

Единственное изменение здесь заключается в том, что publicPath изменен с исходного dist/ на /dist/. Пока добавлен предыдущий путь, относительный адрес не будет найден.


Q5: Как это сделать в реальном проекте?

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

шаг 1: инкапсулируйте компонент LazyLoad

Идеал прекрасен, реальность - это основа

const LazyLoad = (path)=>{
  return Loadable({
    loader: () => import(path),
    loading: Loading,
  })
}

const B = LazyLoad('./b.js')

Затем я получил ошибку

image.png | left | 747x55

Это связано с тем, что при компиляции веб-пакета предварительная доставка импорта == не поддерживает динамические пути ==


Шаг 2: ужасный импорт, узнайте об этом

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


Шаг 3: инкапсулировать не импортируемые части

Поскольку импорт невозможен, он может инкапсулировать только не импортируемую часть.

const LazyLoad = loader => Loadable({
  loader,
  loading:Loading,
})

Отделите часть загрузчика в качестве параметра, следующее конкретное использование

const B = LazyLoad(()=>import('./b.js'));
const C = LazyLoad(()=>import('./c.js'));

Ниже весь код

import React,{Component} from 'react';
import { BrowserRouter as Router, Route, Switch,Link } from 'react-router-dom';
import ReactDom from 'react-dom';
import Loadable from 'react-loadable';

const Loading = (props) => {
  return <div>Loading...</div>
};

const LazyLoad = loader => Loadable({
  loader,
  loading:Loading,
})
const B = LazyLoad(()=>import('./b.js'));
const C = LazyLoad(()=>import('./c.js'));

export default class A extends Component{
  render(){
    return <div>
      <Router>
        <div>
          <Route path="/B" component={B}/>
          <Route path="/C" component={C}/>
          <Link to="/B">to B</Link><br/>
          <Link to="/C">to C</Link>
        </div>
      </Router>
    </div>
  }
}
ReactDom.render(<A/>,document.querySelector("#btn"))
if (module.hot) {
     module.hot.accept()
}

Вышеупомянутый метод упаковки не идеален, в документации к веб-пакету говорится, что он поддерживает: ==импорт(./dynamic/\${path}) способ == Поскольку не все переменные поддерживаются, это зависит от конкретной бизнес-формы.Если все части по запросу находятся в определенном каталоге, эта операция может быть более удобной.

В соответствии с текущим методом это кажется громоздким, но поддержка пути может быть достигнута путем настройки псевдонима alias webpack.


Q6: Загружать +конфигурацию маршрутизатора по требованию

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

шаг 1: инкапсулировать отложенную загрузку

Создайте файл LazyLoad.js

import React from 'react';
import Loadable from 'react-loadable';
const Loading = (props) => {
  return <div>Loading...</div>
};

export default loader => Loadable({
  loader,
  loading:Loading,
})

Сначала отдельно инкапсулируйте компонент Lazyload.


Шаг 2: настроить маршруты

Создать route.js

import LazyLoad from './LazyLoad';
export default [
  {
    path: "/B",
    component: LazyLoad(()=>import('./b.js'))
  },
  {
    path: "/C",
    component: LazyLoad(()=>import('./c.js')),
    routes: [
      {
        path: "/C/D",
        component: LazyLoad(()=>import('./d.js'))
      },
      {
        path: "/C/E",
        component: LazyLoad(()=>import('./e.js'))
      }
    ]
  }
];

Настройте файл маршрутов для динамического импорта маршрутов.


Шаг 3: метод пакетного инструментаRouteWithSubRoutes

Создать utils.js

import React from 'react';
import {Route} from 'react-router-dom';
export const RouteWithSubRoutes = route => (
  <Route
    path={route.path}
    render={props => (
      // pass the sub-routes down to keep nesting
      <route.component {...props} routes={route.routes} />
    )}
  />
);

==Этот шаг особенно важен, особенно важен, особенно важен==

Роль этого метода инструмента заключается в отображении компонента


Шаг 4: Измените запись маршрутизации первого уровня.

import React,{Component} from 'react';
import { BrowserRouter as Router, Route, Switch,Link } from 'react-router-dom';
import ReactDom from 'react-dom';

import {RouteWithSubRoutes} from './utils';
import routes from './routes';

export default class A extends Component{
  render(){
    return <div>
      <Router>
        <div>
          <Link to="/B">to B</Link><br/>
          <Link to="/C">to C</Link>
          {routes.map((route, i) => <RouteWithSubRoutes key={i} {...route} />)}
        </div>
      </Router>
    </div>
  }
}
ReactDom.render(<A/>,document.querySelector("#btn"))
if (module.hot) {
     module.hot.accept()
}
  1. Введение метода инструмента RouteWithSubRoutes
  2. Импортируйте файл конфигурации маршрутизации маршрутов
  3. Отрисовка обхода маршрутов в упакованном файле

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


Шаг 5: Измените запись вторичной маршрутизации

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

import React,{Component} from 'react';
import {RouteWithSubRoutes} from './utils';
import { Link} from 'react-router-dom';

export default ({ routes }) => (
  <div>
    this is C
    <Link to="/C/D">to D</Link>
    <Link to="/C/E">to E</Link>
    {routes.map((route, i) => <RouteWithSubRoutes key={i} {...route} />)}
  </div>
);
  1. Введение метода инструмента RouteWithSubRoutes
  2. Открытая функция принимает параметры route
  3. Маршруты — это конфигурация внутреннего слоя в конфиге, то есть конфигурация маршрутизации второго уровня
  4. Вторичная конфигурация маршрутизации, продолжайте рендеринг через RouteWithSubRoutes

==注意:config嵌套路由,需要逐层,一层一层的通过RouteWithSubRoutes来渲染。 == ==新人很容易忽视这一点! == ==新人很容易忽视这一点! == ==新人很容易忽视这一点! ==


Q7: Используйте роутер по своему усмотрению?

Маршрут настраивается в методе конфига, но на самом деле он может быть и здесь смешанным, то есть смешаны метод конфига + метод компонента. Измените запись вторичного маршрута:

import React from 'react';
import { Link,Route} from 'react-router-dom';
//import {RouteWithSubRoutes} from './utils';
import LazyLoad from './LazyLoad';

const D = LazyLoad(() => import('./d.js'))
const E = LazyLoad(() => import('./e.js'))

export default ({ routes }) => (
  <div>
    this is C
    <Route path="/C/D" component={D}/>
    <Route path="/C/E" component={E}/>
    <Link to="/C/D">to D</Link>
    <Link to="/C/E">to E</Link>
    {/* {routes.map((route, i) => <RouteWithSubRoutes key={i} {...route} />)} */}
  </div>
);

На самом деле, здесь просто делай, что хочешь

Для маршрутизации лучше вести ее равномерно.Конечно, вы также можете выбрать нужный метод в соответствии с бизнесом! .

Загрузка веб-пакета по требованию, которая вызывает головную боль, подошла к концу.