webpack4 упаковывает интерфейсные многостраничные проекты vue

внешний интерфейс JavaScript CSS Webpack

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

1. Использование

Структура проекта следующая:

project
  |- bulid                   <!-- 这个目录是自动生成的-->
       |- public
       |- css
       |- js
       |- page1.html             <!-- 插件生成的html文件-->
       |- page2.html             <!-- 插件生成的html文件-->
       ...
  |- public/                 <!-- 存放字体、图片、网页模板等静态资源-->
  |- src                     <!-- 源码文件夹-->
       |- components/
       |- css/
       |- js/
       |- page1.js               <!-- 每个页面唯一的VUE实例,需绑定到#app-->
       |- page2.js               <!-- 每个页面唯一的VUE实例,需绑定到#app-->
       ...
  |- package.json
  |- package-lock.json
  |- README.md

В общей папке хранятся некоторые статические файлы, а в папке src хранится исходный код. Каждая страница генерирует экземпляр vue через файл ввода (page1.js, page2.js, ..) и монтирует его в элементе #app html-файла, сгенерированного плагином.

Установить зависимости

$ npm install

войти в режим разработки

$ npm run start

браузер откроетсяhttp://localhost:3000, В настоящее время страница пуста, показывая, что слова не могут быть получены. Не паникуйте, добавьте его после URL/page1.html, Нажмите Enter, чтобы увидеть нашу страницу. Это потому, что я установил домашнюю страницу сервера разработки наindex.html, а страница в этом примере — page1.html, page2.html, поэтому она будет отображаться пустой.

Разработка завершена, соберите производственную версию:

$ npm run build

Это сгенерирует сборку/папку, файлы в ней оптимизированы, и ресурсы, на которые отвечает сервер, поступают из этой папки.

2. Введение

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

Наша разработка разделена на производственную среду и среду разработки, поэтому нам нужны 2 файла конфигурации веб-пакета (возможно, вы захотите использовать переменные среды env, а затем использовать оператор 3-объекта для возврата разных значений в соответствии со значением env. Однако этот метод работает в веб-пакете. В свойствах экспортируемого модуля он не работает, я пробовал ~~~). Здесь мы разбиваем на 3 файла, из которыхwebpack.common.jsЭто обычная конфигурация, которая будет использоваться в обеих средах.webpack.dev.jsа такжеwebpack.prod.jsЭто уникальная конфигурация в двух средах. используется здесьwebpack-mergeЭтот пакет сочетает в себе общественную конфигурацию с уникальной конфигурацией.

  • webpack.common.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const VueLoaderPlugin = require('vue-loader/lib/plugin');
const devMode = process.env.NODE_ENV !=='production';

// 需要被打包入口文件数组
// 数组元素类型 {string|object}
// string:将以默认规则生成bundle
// object{filename|title|template} 生成的bundle.html的文件名|title标签内容|路径 /public 下的模板文件(需指定文件后缀)
const entryList = [
    'page1',
    'page2',
];


/**
 * @param {array} entryList
 * @param {object} option:可选  要手动配置的内容
 */
const createEntry = (list = [], option = {}) => {
    const obj = {};
    list.forEach((item) => {
        const name = item.filename ? `./js/${item.filename}` : `./js/${item}`;
        obj[name] = path.resolve(__dirname, './src', `./${item}.js`);
    });
    return Object.assign(obj, option);
};


module.exports = {
    entry: createEntry(entryList),
    output: {
        path: path.resolve(__dirname, './build'),
    },
    module: {
        rules: [
            {
                test: /\.js$/,
                exclude: /(node_modules|bower_components)/,
                use: {
                    loader: 'babel-loader',
                    options: {
                        presets: ['@babel/preset-env'],
                    },
                },
            },
            {
                test: /\.vue$/,
                use: 'vue-loader',
            },
            {
                test: /\.(woff|woff2|eot|ttf|otf)$/,
                use: {
                    loader: 'file-loader',
                    options: {
                        name: 'public/fonts/[name].[ext]',
                    },
                },
            },
            {
                test: /\.(png|svg|jpg|gif)$/,
                use: {
                    loader: 'file-loader',
                    options: {
                        name: 'public/images/[name].[ext]',
                    },
                },
            },
        ],
    },
    plugins: createPluginInstance(entryList).concat([
        // vue SFCs单文件支持
        new VueLoaderPlugin(),
    ]),
};

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

  • webpack.dev.js
const webpack = require('webpack');
const path = require('path');
const merge = require('webpack-merge');
const common = require('./webpack.common.js');

module.exports = merge(common, {
    mode: 'development',
    devtool: 'inline-source-map',
    output: {
        filename: '[name].js',
        chunkFilename: '[name].js',
    },
    module: {
        rules: [
            {
                test: /\.(css|less)$/,
                use: [
                    'vue-style-loader',
                    'css-loader', 
                    'postcss-loader',
                    'less-loader'
                ],
            },
        ],
    },
    resolve: { alias: { vue: 'vue/dist/vue.js' } },
});

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

  • webpack.prod.js
const webpack = require('webpack');
const merge = require('webpack-merge');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const UglifyJsPlugin = require("uglifyjs-webpack-plugin");
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
const common = require('./webpack.common.js');

module.exports = merge(common, {
    mode: 'production',
    output: {
        filename: '[name].[contenthash].js',
        chunkFilename: '[name].[contenthash].js',
    },
    resolve: { alias: { vue: 'vue/dist/vue.min.js' } },
    module: {
        rules: [
            {
                test: /\.(css|less)$/,
                use: [
                    MiniCssExtractPlugin.loader, 
                    'css-loader', 
                    'postcss-loader',
                    'less-loader'
                ],
            },
        ],
    },

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

2.2 Решите каталог вывода файла

Мы ожидаем, что папка сборки будет иметь следующую структуру:

build
  |- css/
  |- js/
  |- page1.html
  |- page2.html
  ...

То есть файлы скомпонованы по типу, а html файлы размещены прямо в этом каталоге, но результаты вывода нашей конфигурации выше смешаны между собой. Поскольку атрибут name может быть либо именем файла, либо/dir/aНапример, имена файлов с путями, мы вносим некоторые изменения в соответствии с этой функцией.

  • Измените путь вывода непосредственно на вывод

например, переход наbuild/js, другие ресурсы используют относительные пути, такие как../page1.htmlмодифицировать.我一开始就这样做的,但最终会导致开发服务器无法响应文件的变化,因为他只能针对输出目录下的文件进行监听,该目录之上的文件变化无能为力。

  • Изменить имя записи

Это также наше окончательное решение. исходное имя файлаpage1превратиться в/js/page1, окончательные выходные файлы js будут помещены в папку js. В производственной среде мы используем плагин MiniCssExtractPlugin для извлечения css в файл js.Это конфигурация плагина:

new MiniCssExtractPlugin({
            filename:'[name].[contenthash].css'
        })

Имя здесь - это имя исходного входа. Из-за изменения имени входа вышеуказанное в конечном итоге станетjs/page1.131de8553ft82.css, а заполнитель [имя] действителен только во время компиляции, что означает, что этим значением нельзя управлять с помощью функций. Таким образом, вы не можете использовать заполнитель [name] для того, что хотите, просто используйте [id].

new MiniCssExtractPlugin({
            filename:'/css/[id].[contenthash].css'
        })

3. Кодовое разделение

Разделить с помощью Optimization.splitChunks в webpack4.

//webpack.common.js
const path = require('path');

module.exports = {
   // ... 省略其他内容
    optimization:{
        runtimeChunk:{
            name:'./js/runtime'
        },
        splitChunks:{
            // 避免过度分割,设置尺寸不小于30kb
            //cacheGroups会继承这个值
            minSize:30000,
            cacheGroups:{
                //vue相关框架
                main:{
                    test: /[\\/]node_modules[\\/]vue[\\/]/,
                    name: './js/main',
                    chunks:'all'
                },
                //除Vue之外其他框架
                vendors:{
                    test:/[\\/]node_modules[\\/]?!(vue)[\\/]/,
                    name: './js/vendors',
                    chunks:'all'
                },
                //业务中可复用的js
                extractedJS:{
                    test:/[\\/]src[\\/].+\.js$/,
                    name:'./js/extractedJS',
                    chunks:'all'
                }
                
            }
        }
    }
};

runtimeChunk содержит некоторые шаблонные файлы для webapck, так что вы можете запаковать, не меняя содержимое исходного файла, значение хеша все равно меняется, поэтому мы извлекаем его отдельно,кликните сюдапонять больше. cacheGroups используется для извлечения мультиплексированных модулей, и тест будет пытаться сопоставить (模块的绝对路径||模块名), возвращаемое значение истинно и удовлетворяетусловиеМодуль будет разделен. Условие можно настроить, например, минимальный модуль должен быть намного больше, по крайней мере, сколько Chunk (т.е. количество раз) и т. д. По умолчанию передний модуль не меньше 30кб.

4. Сотрясение дерева

Добавьте в package.json

"sideEffects":["*.css","*.less","*.sass"]

Файлы вне этого массива будуткачание дереваВоздействие. Неиспользуемый код будет удален из объекта экспорта. Это значительно уменьшит бесполезный код. Если встряхивание дерева имеет побочные эффекты для некоторых файлов, поместите эти имена файлов в массив, чтобы пропустить эту операцию. Файлы CSS (в т.ч. .less, .sass) нужно ставить, иначе стили потеряются.

5. Использование плагинов

5.1 clean-webpack-plugin

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

5.2 html-Webpack-plugin

В нашем каталоге исходного кода нет html-файла. Мы используем упакованные несколько html-файлов.плагинСгенерировано.

//webpack.common.js
// ...省略上面已经出现过的内容

//每个html需要一个插件实例
//批量生成html文件
const createPluginInstance = (list = []) => (
    list.map((item) => {
        return new HtmlWebpackPlugin({
            filename: item.filename ? `${item.filename}.html` : `${item}.html`,
            template: item.template ? `./public/${item.template}` :             './public/template.html',
            title: item.title ? item.title : item,
            chunks: [
                `./js/${item.filename ? item.filename : item}`,
                './js/extractedJS',
                './js/vendors',
                './js/main',
                './js/runtime',
                './css/styles.css',
                devMode ? './css/[id].css' : './css/[id].[contenthash].css',
            ],
        });
    })
);

По умолчанию все входные файлы и файлы с разделением кода будут упакованы в html-файл, если указатьchunksсвойства, чтобы сообщить плагину只包含Какие фрагменты или excludeChunks указывают, какие фрагменты не следует включать. Здесь есть небольшая проблема, мы не можем сделать так, чтобы файл содержал именно те куски, которые ему нужны. Если вы хотите не включать неиспользуемые фрагменты, вы можете настроить его только вручную в соответствии с реальной ситуацией.Файлы, сгенерированные этой функцией в пакетном режиме, всегда будут содержать все общедоступные файлы упаковки.

5.3 mini-css-extract-plugin (prooduction)

Этот плагин используется для извлечения css из файлов js в отдельные файлы css.

//webpack.prod.js
//...省略其他内容
plugins:[
        new CleanWebpackPlugin('build'), 
        // 提取css
        new MiniCssExtractPlugin({
            filename:'./css/[id].[contenthash].css'
        }),
        //优化缓存
        new webpack.HashedModuleIdsPlugin()
    ]   

5.4 оптимизация-css-assets-webpack-plugin (рабочая версия)

Используется для упрощения упакованного кода css, задается в свойстве минимизации оптимизации конфигурации, чтопокрытиеWebpack установлен по умолчанию, поэтому вам также следует установить инструмент оптимизации js (здесь мы используем плагин uglifyplugin):

optimization: {
        minimizer:[
          new UglifyJsPlugin({
            cache: true,
            parallel: true
          }),
          new OptimizeCSSAssetsPlugin()
        ]
    }

6. Сервер разработки, горячая замена модулей (разработка)

Добавьте следующий контент в webpack.dev.js:

//...省略其他内容
devServer:{
        index:'index.html',
        hot:true,
        contentBase:path.resolve(__dirname,'./build'),
        port:3000,
        noInfo:true
    },
plugins:[
        new webpack.HotModuleReplacementPlugin()
    ]

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

7. Другие

public используется для хранения статических ресурсов.После упаковки в build/ будет создана одноименная папка, в которой хранятся ресурсы, которые будут использоваться public. Если вы ссылаетесь на общедоступные ресурсы, такие как изображения, в файле .css, используйте абсолютный путь при добавлении URL-адреса:

<!-- src/css/page1.css -->
.bg-img {
    background-image:url(/public/images/1.jpg)
}

Таким образом, его можно использовать в обычном режиме при открытии через http/https.Если его открыть как файл (например, дважды щелкнуть page1.html после упаковки), вы обнаружите, что браузер показывает, что ресурс не может быть найден . Импортируя изображение как ссылку на переменную (import name from path), можно использовать как абсолютные, так и относительные пути.

Что не так, добро пожаловать, если вам это нравится, вы можетеподобно.