Использование опции splitChunks в webpack для кеширования большого файла

tl; dr

Используя url-loaderWebpack, я загружаю около 2300 иконок в формате PNG размером 40 на 30 пикселей (маленькие спрайты покемонов) в массив, в результате чего получается ~ 2,2 МБ минимизированного JavaScript. Эти образы и модуль, который их обертывает, практически никогда не меняются и не имеют изменяющихся зависимостей. Я хочу вывести их в отдельный файл js, чтобы при запуске webpack --watch каждый раз, когда я меняю код, который зависит от этого массива изображений, Webpack не запускал повторное извлечение и минификацию этих изображений, процесс, который берет на себя порядка 20 секунд.

Раньше для подобных вещей я использовал отдельную точку входа и CommonsChunkPlugin, как описано в разделе Разделение кода документации Webpack. (Я не знаю, так ли было использовано предполагаемый, но он работал.) Это больше не жизнеспособно, поскольку Webpack 4 устарел CommonsChunkPlugin, но я не могу понять, как использовать его замену, config.optimization.splitChunks, для добиться того же эффекта.


По какой-то причине Webpack отказался от своего CommonsChunkPlugin в пользу конфигурации webpack.optimization.splitChunks, которая с помощью CommonsChunkPlugin выдает ошибку при инициализации, а затем не обновляет свой документация по разделению кода, чтобы отразить это изменение. Я пару часов искал официальную документацию по splitChunks и о том, как ее использовать, и ближе всего я нашел эти "RIP CommonsChunkPlugin" суть и Статья на Medium.com, которые дают только высокоуровневые объяснения изменений.

client/pokemon-icons.js

import Pokedex from 'pokedex'; // has no dependencies

class PokemonIcons {
    constructor() {
        let req = require.context('../resources/icons/regular', false, /\.png$/);
        req.keys().forEach(fn => {
            let species = /([-a-zA-Z0-9]+)\.png$/.exec(fn)[1];
            this[species] = { regular: req(fn) };
        });

        req = require.context('../resources/icons/shiny', false, /\.png$/);
        req.keys().forEach(fn => {
            let species = /([-a-zA-Z0-9]+)\.png$/.exec(fn)[1];
            this[species].shiny = req(fn);
        });

        let egg = require('../resources/icons/egg.png');
        this.egg = { regular: egg, shiny: egg };
    }

    getIcon(pokemon) {
        return this[Pokedex.FileNames[pokemon.species]][pokemon.shiny];
    }
}

const icons = new PokemonIcons();
export default icons;

В документации есть несколько способов сообщить Webpack о необходимости извлечения кода в собственный файл фрагментов.

require.ensure()

«Старый» метод использовал require.ensure(), который принимает массив модулей для загрузки и обратный вызов, вызываемый после загрузки модулей.

client/soullink/index.js пример

require.ensure(['../pokemon-icons'], () => {
    const config = require('config.json');
    if (config.someFlag) {
        require('./some-js'); // does not rely on `pokemon-icons.js`
    } else {
        require('./some-other-js'); // has dependencies which rely on `pokemon-icons.js`
    }
});

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

import()

«Новый» метод использует import(), который возвращает Promise. Необязательно, с babel-ployfill и babel-preset-2017 от babel, мы можем использовать синтаксис await.

Синтаксис Promise

import('../pokemon-images').then(icons => {
    let icon = icons.getIcon(...);
    // ...
});

Синтаксис await

async function getIcon(...args) {
    let icons = await import('../pokemon-images');
    return icons.getIcon(...args);
}

Я не могу заставить эти методы даже создать отдельный файл фрагмента, не говоря уже о его кешировании.

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

Конфиги Webpack пробовал

Я не знаю, имеет ли это значение, но я использую HtmlWebpackPlugin, который генерирует файл HTML с помощью ejs-loader, а затем вставляет ссылку на точки входа, перечисленные в массиве опций chunks плагина.

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

Базовый webpack.babel.config.js

Это соответствующие части моего файла конфигурации веб-пакета.

import webpack from 'webpack';
import fs from 'fs';
import path from 'path';
import HtmlWebpackPlugin from 'html-webpack-plugin';

function genConfig(env, options) {
    return {
        entry: {
            index: './client/slot-display/index',
            soullink: './client/soullink-manager/index', // this is the entry that requires pokemon-icons.js
            vendors: [ 'lodash' ],
        },

        output: {
            filename: '[name].js',
            chunkFilename: '[name].js',
            path: path.resolve(__dirname, 'public')
        },

        module: {
            rules: [
                {
                    test: /\.js$/,
                    exclude: /node_modules/,
                    loader: 'babel-loader',
                },
                {
                    test: /\.ejs$/,
                    loader: 'ejs-loader',
                },
                {
                    test: /\.png$/,
                    use: [
                        'url-loader'
                    ]
                }
            ],
        },

        plugins: [
            new webpack.ProvidePlugin({
                _: 'lodash',
            }),
            new HtmlWebpackPlugin({
                template: '!!ejs-loader!./client/slot-display/index.ejs',
                filename: 'index.html',
                chunks: ['index'],
                inject: 'body',
                cache: true
            }),
            new HtmlWebpackPlugin({
                template: '!!ejs-loader!./client/soulLink-manager/index.ejs',
                filename: 'soullink/index.html',
                chunks: ['soullink'],
                inject: 'body',
                cache: true
            })
        ]
    };
}

export default genConfig;

Я пробовал добавить

optimization: {
    splitChunks: {
        chunks: 'all', // with and without this line
        cacheGroups: { // with and without this object
            default: false, // with and without this line
            pokemonIcons: {
                test: /pokemon-icons\.js$/, // with and without this line
                name: 'pokemon-icons', // also 'pokemonIcons'
                minChunks: Infinity
            }
        }
    }
}

а также

entry: {
    pokemonIcons: './client/pokemon-icons', // with and without this separate entry
    // the various soullink entries I've tried:
    soullink: './client/soullink-manager/index',
    soullink: [ './client/pokemon-icons', './client/soullink-manager/index' ], 
    soullink: [ 'babel-polyfill', './client/soullink-manager/index' ],
    soullink: [ 'babel-polyfill', './client/pokemon-icons', './client/soullink-manager/index' ],
}

и при необходимости настройте:

plugins: [
    new HtmlWebpackPlugin({
        // ...
        chunks: ['pokemonIcons', 'soullink'],
        // ...
    }
]

Цитата: вам нужен только фрагмент времени выполнения при использовании долгосрочного кэширования и загрузки по запросу. optimization.runtimeChunk: true И я думаю, что вам не нужно указывать поставщика в качестве точки входа, поскольку wp4 имеет собственный алгоритм для его генерации из существующих точек входа, но на эту часть можно повлиять с помощью конфигурации splitchunks.

Legends 30.03.2018 21:57
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
1
1
630
0

Другие вопросы по теме