Полный анализ конфигурации Webpack (базовый)

Webpack

Благодаря своим мощным функциям веб-пакет стал самым популярным и активным инструментом упаковки, а также «мягким навыком», которым старшие программисты должны овладеть во время собеседований; автор объединяет опыт проекта, чтобы представить использование веб-пакета; эта статья вводная статья, основная Введение в ввод и вывод webpack, использование различных загрузчиков и плагинов, а также построение среды разработки Для технических специалистов, пожалуйста ctrl+w.

  Все демо-коды в этой статье находятся вWebpackDemo

концепция

   Давайте посмотрим на определение webpack на официальном сайте:

По сути, webpack — это сборщик статических модулей для современных приложений JavaScript. Когда webpack обрабатывает приложение, он рекурсивно строит граф зависимостей, содержащий каждый модуль, необходимый приложению, а затем объединяет все эти модули в один или несколько пакетов.

Во-первых, webpack — это упаковщик статических модулей, к так называемым статическим модулям относятся скрипты, таблицы стилей, картинки и т. д. При упаковке webpack сначала проходит все статические ресурсы, строит граф зависимостей по ссылкам на ресурсы. , а затем объединяет модули Разделить и упаковать один или несколько пакетов. Давайте еще раз взглянем на картинку на официальном сайте, которая наглядно описывает процесс:

logo.png

Когда вы упоминаете WebPack, вы должны упомянуть четыре Webpack.Основная идея

  • Запись: указывает, какой модуль веб-пакет должен использовать в качестве начала построения своего внутреннего графа зависимостей.
  • output: куда выводить созданные им пакеты
  • загрузчик: позволяет веб-пакету обрабатывать файлы, отличные от JavaScript
  • Плагины: используются для выполнения более широкого круга задач

твой первый упаковщик

   Сначала устанавливаем вебпак глобально:

npm install webpack webpack-cli –g

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

webpack <entry> [<entry>] -o <output>

   Запись и вывод здесь соответствуют записи и вводу в вышеприведенных понятиях Давайте создадим новый файл ввода:

//demo1/index.js
var a = 1
console.log(a)
document.write('hello webpack')

  С входным файлом нам также необходимо определить входной путь dist/bundle.js через командную строку:

webpack index.js -o dist/bundle.js

   Таким образом, webpack создаст упакованные файлы в каталоге dist.

demo1-result.png

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

конфигурационный файл

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

webpack [--config webpack.config.js]

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

//生产环境配置
webpack --config webpack.prod.config.js
//开发环境配置
webpack --config webpack.dev.config.js

Несколько типов конфигурации

  конфигурационный файл черезmodule.exportsЭкспорт объекта конфигурации:

//webpack.config.js
var path = require('path');
module.exports = {
  mode: 'development',
  //入口文件
  entry: './index.js',
  //输出目录
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'bundle.js'
  }
};

Помимо экспорта как объекта, его также можно экспортировать как функцию.Функция будет вносить параметры, такие как переменные среды, переданные в командной строке, чтобы переменные среды можно было настроить более удобно, например, мы находимся в формальная среда и линия онлайн-упаковки. Среда разработки может бытьenvРазличать:

var path = require('path');
//env:环境对象
module.exports = function(env, argv){
  return {
    //其他配置
    entry: './index.js',
    output: {}
  }
};

   также можно экспортировать как промис для асинхронной загрузки конфигурации, например, файл записи может загружаться динамически:

module.exports = () => {
    return new Promise((resolve, reject)=>{
        setTimeout(()=>{
            resolve({
                entry: './index.js',
                output: {}
            })
        }, 5000)
    })
}

Вход

   Как упоминалось выше, запись является отправной точкой всей зависимости; наша часто используемая конфигурация с одной записью — это запись страницы:

module.exports = {
    entry: './index.js',
}

   Это сокращение от:

module.exports = {
    entry: {
        main: './index.js'
    },
}

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

module.exports = {
    entry: [
        //轮播图模块
        './src/banner.js',
        //主模块
        './src/index.js', 
        //底部模块
        './src/foot.js'
    ],
}

Иногда у нас есть проект, может иметь более одной страницы, вам нужно упаковать несколько страниц, а вход поддерживает форму входящих объектов, код находится в DEMO3:

//demo3
module.exports = {
    entry: {
        home: './src/home.js',
        list: './src/list.js',
        detail: ['./src/detail.js', './src/common.js'],
    },
}

   Таким образом, webpack создаст три разные зависимости.

выход

  outputПараметры используются для управления тем, как webpack импортирует скомпилированные файловые модули; хотя записей может быть несколько, настроить можно только одну.output:

module.exports = {
    entry: './src/index.js',
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'bundle.js',
        //CDN地址
        publicPath: '/',
    },
}

   Здесь настраиваем однократную запись, а на выходе bundle.js, но если стоит режим множественной записи, то не получится, webpack подскажетConflict: Multiple chunks emit assets to the same filename, то есть несколько файловых ресурсов имеют одно и то же имя файла; webpack предоставляет占位符Чтобы гарантировать, что каждый выходной файл имеет уникальное имя:

module.exports = {
    entry: {
        home: './src/home.js',
        list: './src/list.js',
        detail: ['./src/detail.js', './src/common.js'],
    },
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: '[name].bundle.js',
    },
}

   Таким образом, файлы, упакованные webpack, будут упакованы в соответствии с именем входного файла для создания трех разных файлов пакета; также есть следующие разные строки-заполнители:

Заполнитель описывать
[hash] хэш идентификатора модуля
[chunkhash] хэш содержимого чанка
[name] имя модуля
[id] идентификатор модуля
[query] Запрос модуля, например, строка после имени файла?

   Здесь вводятся понятия Module, Chunk и Bundle.Эти два существительных часто встречаются в приведенном выше коде, так в чем же между ними разница? Прежде всего, мы обнаружили, что в нашем коде часто появляются модули, такие как module.exports, при этом Chunk часто появляется с записью, а Bundle всегда появляется с выводом.

  • модуль: исходный код, который мы пишем, будь то commonjs или amdjs, можно понимать как модули один за другим
  • Чанк: Когда исходный файл модуля, который мы написали, отправляется в webpack для упаковки, webpack генерирует файлы фрагментов в соответствии с отношением ссылки на файл, и веб-пакет выполняет некоторые операции с этими файлами фрагментов.
  • Пакет: После того, как webpack обработает файл фрагмента, он, наконец, выведет файл пакета, который содержит окончательные исходные файлы, которые были загружены и скомпилированы, поэтому его можно запустить непосредственно в браузере.

   Давайте лучше разберемся в этих трех концепциях с помощью следующей картинки:

chunk-bundle.png

Суммировать:

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

хэш, чанкхэш, контентхэш

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

  • Хэш: это связано с построением всего проекта. Пока в проекте есть изменения файлов, значение хеш-функции всего построения проекта будет меняться, и все файлы имеют одно и то же значение хеш-функции.
  • Chunkhash: Это связано с созданием входного файла.Соответствующий фрагмент строится в соответствии с входным файлом, и генерируется хэш, соответствующий каждому фрагменту; если входной файл изменен, значение хеш-функции соответствующего фрагмента будет измененный.
  • contenthash: это связано с содержимым самого файла, и уникальный хэш создается в соответствии с содержимым файла, то есть, когда содержимое файла изменяется, хеш изменяется.

модель

В webpack2 и webpack3 нам нужно вручную добавлять плагины для сжатия кода, определять переменные среды и обращать внимание на оценку среды, что очень громоздко; в webpack4 конфигурация режима предоставляется напрямую, что может быть используется из коробки, при игнорировании конфига webpack также выдаст предупреждение.

module.exports = {
  mode: 'development',
};
//相当于
module.exports = {
   devtool:'eval',
   plugins: [
      new webpack.NamedModulesPlugin(),
      new webpack.NamedChunksPlugin(),
      new webpack.DefinePlugin({ 
        "process.env.NODE_ENV": JSON.stringify("development") 
      })
   ]
}

Режим разработки    должен сообщить webpack, что я в настоящее время нахожусь в состоянии разработки, то есть упакованный контент должен быть дружественным к разработке, что удобно для отладки кода и обновлений браузера в реальном времени.

module.exports = {
  mode: 'production',
};
//相当于
module.exports = {
   plugins: [
      new UglifyJsPlugin(/*...*/),
      new webpack.DefinePlugin({ 
        "process.env.NODE_ENV": JSON.stringify("production") 
      }),
      new webpack.optimize.ModuleConcatenationPlugin(),
      new webpack.NoEmitOnErrorsPlugin()
   ]
}

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

   Полагаю, у многих ботинок детей возникали вопросы, почему здесь используется DefinePlugin при определении переменных окруженияJSON.stringify("production"), использовать напрямую"production"Разве это не проще?

   Сначала посмотримJSON.stringify("production")что генерируется; результат выполнения""production"", тут обратите внимание, дело не в том, что у вас замылены глаза или на экране маленькие черные точки, результат действительно лучше, чем"production"Вложен дополнительный слой кавычек.

   Мы можем просто понимать плагин DefinePlugin какprocess.env.NODE_ENVзаменить на строку内容. Предположим, у нас есть следующий код для оценки окружения в коде:

//webpack.config.js
module.exports = {
  plugins: [
    new webpack.DefinePlugin({ 
      "process.env.NODE_ENV": "production"
    }),
  ]
}
//src/index.js
if (process.env.NODE_ENV === 'production') {
    console.log('production');
}

   Сгенерированный код будет скомпилирован следующим образом:

//dist/bundle.js
//代码中并没有定义production变量
if (production === 'production') {
    console.log('production');
}

   но мы не можем определить это в нашем кодеproductionпеременная, поэтому код будет напрямую сообщать об ошибке, поэтому нам нужно обернуть слой через JSON.stringify:

//webpack.config.js
module.exports = {
  plugins: [
    new webpack.DefinePlugin({ 
      //"process.env.NODE_ENV": JSON.stringify("production")
      //相当于
      "process.env.NODE_ENV": '"production"'
    }),
  ]
}
//dist/bundle.js
if ("production" === 'production') {
    console.log('production');
}

  С скомпилированным таким образом кодом проблем не возникает.

Автоматически генерировать страницы

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

npm install --save-dev html-webpack-plugin

  В demo3 мы создали три разных файла bundle.js, мы надеемся представить эти три файла на трех разных страницах, изменив файл конфигурации следующим образом:

module.exports = {
    //省略其他代码
    plugins: [
        new HtmlWebpackPlugin({
            //引用的模板文件
            template: './index.html',
            //生成的html名称
            filename: 'home.html',
            chunks: ['home']
        }),
        new HtmlWebpackPlugin({
            template: './index.html',
            filename: 'list.html',
            chunks: ['list']
        }),
        new HtmlWebpackPlugin({
            template: './index.html',
            filename: 'detail.html',
            chunks: ['detail']
        }),
    ]
}

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

  html-webpack-plugin также поддерживает следующие поля:

new HtmlWebpackPlugin({
    template: './index.html',
    filename: 'all.html',
    //页面注入title
    title: 'html webpack plugin title',
    //默认引入所有的chunks链接
    chunks: 'all',
    //注入页面位置
    inject: true,
    //启用hash
    hash: true,
    favicon: '',
    //插入meta标签
    meta: {
        'viewport': 'width=device-width, initial-scale=1.0'
    },
    minify: {
        //清除script标签引号
        removeAttributeQuotes: true,
        //清除html中的注释
        removeComments: true,
        //清除html中的空格、换行符
        //将html压缩成一行
        collapseWhitespace: false,
        //压缩html的行内样式成一行
        minifyCSS: true,
        //清除内容为空的元素(慎用)
        removeEmptyElements: false,
        //清除style和link标签的type属性
        removeStyleLinkTypeAttributes: false
    }
}),

   После установки заголовка выше вам необходимо установить строку шаблона в файле шаблона:

<title><%= htmlWebpackPlugin.options.title %></title>

loader

Загрузчик используется для преобразования исходного кода модуля модуля.По умолчанию веб-пакет может распознавать только код commonjs, но мы будем внедрять в код такие файлы, как vue, ts, less и т. д., которые веб-пакет не может обработать, загрузчик расширяется webpack для обработки различных файлов. Возможность печатать, конвертировать эти файлы в js, css, которые может отображать браузер.

  module.rulesПозволяя нам настроить несколько загрузчиков, мы можем четко видеть, какие загрузчики применяются к текущему типу файла, а код загрузчиков находится в demo4.

{
    module: {
        rules: [
            {
                test: /\.js$/,
                use: {
                    loader: 'babel-loader',
                    options: {}
                }
            },
            {
                test: /\.css$/,
                use: [
                    { loader: 'style-loader' },
                    { loader: 'css-loader' }
                ]
            },
        ]
    }
}

Мы видим, что значение атрибута правил представляет собой массив, каждый объект массива представляет собой другое правило совпадения; регулярное выражение, когда свойство теста отличается, сопоставление различных файловых суффиксов; использование использования означает, что называется, когда файл называется? Существует несколько погрузчик, использует использование в массивах.

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

css-загрузчик и загрузчик стилей

CSS-Loader и Style-Loader похожи по названиям, но возможности обоих имеют большую разницу, но они часто их используют; способы установки:

npm i -D css-loader style-loader

  css-loader используется для объяснения @import и url(); style-loader используется для передачи таблицы стилей, сгенерированной css-loader, через<style>标签, вставьте его на страницу.

/* /src/head.css */
.head{
    display: flex;
    background: #666;
}
/* /src/foot.css */
.foot{
    background: #ccc;
}
/* /src/index.css */
@import './head.css';
@import './foot.css';
.wrap {
    background: #999;
}

   Затем импортируйте index.css в файл ввода, и вы увидите эффект упаковки.На страницу вставляются три тега стиля, а код находится в demo4:

css-loader.png

sass-загрузчик и меньше-загрузчик

   Эти два загрузчика можно догадаться из названия, они используются для работы с sass и less стилями. способ установки:

npm i -D sass-loader less-loader node-sass

   Настроить в конфиге, код есть в demo4:

{
    //其他配置
    rules: {
        test: /\.scss$/,
        use: [{
            loader: 'style-loader'
        }, {
            loader: 'css-loader'
        },{
            loader: 'sass-loader'
        }]
    },{
        test: /\.less$/,
        use: [{
            loader: 'style-loader'
        }, {
            loader: 'css-loader'
        },{
            loader: 'less-loader'
        }]
    }
}

postcss-loader

На дворе 0202 год, и мои друзья точно не хотят вручную добавлять -moz, -ms, -webkit и другие приватные префиксы браузера один за другим; postcss предоставляет множество функций расширения для стилей; ничего не говори, установи его первый:

npm i -D postcss-loader

  Старое правило, либо настроить его в конфиге:

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

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

why

Это связано с тем, что у postcss есть только две основные функции: первая — преобразовать CSS в абстрактное синтаксическое дерево AST, которым можно манипулировать с помощью JS, а вторая — вызвать плагины для обработки AST и получения результатов; поэтому postcss обычно обрабатывает CSS через плагины и не будет обрабатывать его напрямую, поэтому сначала нам нужно установить некоторые плагины:

npm i -D autoprefixer postcss-plugins-px2rem cssnano

.browserslistrc

> 0.25%
last 2 versions

postcss.config.js

module.exports = {
    plugins: [
        //自动添加前缀
        require('autoprefixer'),
        //px转为rem,应用于移动端
        require('postcss-plugins-px2rem')({ remUnit: 75 }),
        //优化合并css
        require('cssnano'),
    ]
}

autoprefixer

babel-loader

npm i -D babel-loader @babel/core @babel/preset-env @babel/plugin-transform-runtime
npm i -S @babel/runtime

{
    //省略其他配置
    rules: [{
        test: /\.js/,
        use: {
            loader: 'babel-loader'
        }
    }]
}

.babelrc

{
    "presets": [
        "@babel/preset-env"
    ],
    "plugins": [
        "@babel/plugin-transform-runtime"
    ]
}

{
    //省略其他配置
    rules: [{
        test: /\.js$/,
        use: {
            loader: 'babel-loader'
        },
        // exclude: /node_modules/,
        include: [path.resolve(__dirname, 'src')]
    }]
}

$

npm i file-loader url-loader -D

{
    //省略其他配置
    rules: [{
        test: /\.(png|jpg|gif|jpeg|webp|svg|eot|ttf|woff|woff2)$/,
        use: {
            loader: 'url-loader',
            options: {
                //10k
                limit: 10240,
                //生成资源名称
                name: '[name].[hash:8].[ext]',
                //生成资源的路径
                outputPath: 'imgs/'
            },
            exclude: /node_modules/,
        }
    }]
}

body{
    width: 100vw;
    height: 100vh;
    background: url(./bg.png) no-repeat;
    background-size: 400px 400px;
    background-position: center center;
}

url-loader.png

html-withimg-loader

npm i -D html-withimg-loader

{
    //省略其他配置
    rules: [{
        test: /\.(htm|html)$/,
        use: {
            loader: 'html-withimg-loader'
        }
    }]
}

html-withimg-loader.png

use: {
    loader: 'url-loader',
    options: {
        //10k
        limit: 10240,
        esModule: false
    }
}

<%= htmlWebpackPlugin.options.title %>

<img src="<%=require('./src/bg1.png') %>" alt="" srcset="">

vue-loader

npm i -D vue-loader vue-template-compiler
npm i -S vue

//src/App.vue
<template>
    <div id="app">
        <div class="box" @click="tap">{{title}}</div>
    </div>
</template>
<script>
export default {
    name: 'app',
    data(){
        return {
            title: 'app实例'
        }
    },
    methods: {
        tap(){
            this.title = this.title.split('').reverse().join('')
        }
    }
}
</script>
<style>
#app{
    font-size: 16px;
    background: #ccc;
}
</style>

//src/main.js
import Vue from 'vue'
import App from './App.vue'

new Vue({
    render: h => h(App)
}).$mount('#app')

.vue

const VueLoaderPlugin = require('vue-loader/lib/plugin')
module.exports = {
    module: {
        rules: [
        //省略其他loader
        {
            test: /\.vue$/,
            loader: 'vue-loader'
        }]
    },
    plugins: [
        new VueLoaderPlugin(),
    ]
}

static-server

npm i -D webpack webpack-dev-server

webpack.dev.config.js

module.exports = {
    //省略其他配置
    devServer: {
        //启动服务器端口
        port: 9000,
        //默认是localhost,只能本地访问
        host: "0.0.0.0",
        //自动打开浏览器
        open: false,
        //启用模块热替换
        hot: true,
        //启用gzip压缩
        compress: true
    },
    plugins: [
        //热更新插件
        new webpack.HotModuleReplacementPlugin({
        })
    ]
}

webpack-dev-server

contentBase: [
  path.join(__dirname, "public"),
  path.join(__dirname, "assets")
]

dev-server.png

source-map.png

cheap-module-eval-source-mapsource-map

module.exports = {
    devtool: 'cheap-module-eval-source-map',
    //其他配置
}

devtool.png

plugins

clean-webpack-plugin

npm i -D clean-webpack-plugin

const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
    //其他配置
    plugins: [
        new CleanWebpackPlugin(),
        new HtmlWebpackPlugin({
            template: './public/index.html',
            filename: 'index.html',
        })
    ]
}

mini-css-extract-plugin

npm i -D mini-css-extract-plugin

const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
    //其他配置
    module: {
        rules: [
            {
                test: /\.less/,
                use: [{
                    loader: isDev ? 'style-loader' : MiniCssExtractPlugin.loader
                },{
                    loader: 'css-loader'
                },{
                    loader: 'less-loader'
                }]
            }
        ]
    },
    plugins: [
        new MiniCssExtractPlugin({
            filename: "[name].[hash:8].css",
        })
    ]
}

output.filename

optimize-css-assets-webpack-plugin

production

npm i optimize-css-assets-webpack-plugin -D

const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
module.exports = {
    //其他配置
    plugins: [
        new OptimizeCSSAssetsPlugin(),
    ]
}

copy-webpack-plugin

npm i -D copy-webpack-plugin

const CopyWebpackPlugin = require('copy-webpack-plugin');
module.exports = {
    plugins: [
        new CopyWebpackPlugin({
            patterns: [
                {
                    from: 'public/js/*.js',
                    to: path.resolve(__dirname, 'dist', 'js'),
                    flatten: true,
                }
            ]
        }),
    ]
}

ProvidePlugin

import $ from 'jquery'
$('.box').html('box')

$

module.exports = {
    plugins: [
        new webpack.ProvidePlugin({
            $: 'jquery',
            jQuery: 'jquery'
        }),
    ]
}

no-idea.png

【前端壹读】