webpack: от входа до реальной конфигурации проекта

JavaScript Webpack

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

В этой статье используется версия Webpack 3.6.0.Эта статья разделена на две части. Первый шаг — просто использовать веб-пакет, а вторая часть настраивает веб-пакет через реальный проект, без использования какого-либо интерфейса командной строки, пошаговую настройку, пока производственный код не будет упакован.Это склад, соответствующий этому проекту, каждый раздел в основном соответствует коммиту.

Это план этой статьи, если вам интересно, вы можете прочитать его ниже

Что такое вебпак

С появлением модуляризации вы можете разделить исходный код на модули, но это вызвало проблему. Каждый файл JS должен быть получен с сервера, что замедлит загрузку. Основное предназначение Webpack — решить эту проблему, упаковав все маленькие файлы в один или несколько больших файлов, это очень хорошо иллюстрируют картинки на официальном сайте, кроме того, Webpack — это еще и инструмент, позволяющий использовать различные интерфейсы. Инструменты для новых технологий.

Простой в использовании

Установить

Введите в командной строке

mkdir  webpack-demo
cd webpack-demo
// 创建 package.json,这里会问一些问题,直接回车跳过就行
npm init 
//  推荐这个安装方式,当然你也安装在全局环境下
// 这种安装方式会将 webpack 放入 devDependencies 依赖中
npm install --save-dev webpack

Затем создайте файл, как показано ниже.

Напишите код в следующем файле

// sum.js
// 这个模块化写法是 node 环境独有的,浏览器原生不支持使用
module.exports = function(a, b) {
    return a + b
}
// index.js
var sum = require('./sum')
console.log(sum(1, 2))
<!DOCTYPE html>
<html lang="en">
<head>
    <title>Document</title>
</head>
<body>
    <div id="app"></div>
    <script src="./build/bundle.js"></script>
</body>
</html>

Теперь приступим к настройке самого простого вебпака, для начала создадимwebpack.config.jsфайл, а затем напишите следующий код

// 自带的库
const path = require('path')
module.exports = {
    entry:  './app/index.js', // 入口文件
    output: {
      path: path.resolve(__dirname, 'build'), // 必须使用绝对地址,输出文件夹
      filename: "bundle.js" // 打包后输出文件的文件名
    }
  }

Теперь мы можем начать использовать webpack, набрав в командной строке

node_modules/.bin/webpack

Если все в порядке, вы должны увидеть что-то вроде

Можно обнаружить, что исходные два JS-файла весят всего 100 Б, но после упаковки они выросли до 2,66 Кбайт. Среди них Webpack должен что-то сделать.bundle.jsПосмотрите в файле.

После упрощения кода основная идея заключается в следующем.

var array = [(function () {
        var sum = array[1]
        console.log(sum(1, 2))
    }),
    (function (a,b) {
        return a + b
    })
]
array[0]() // -> 3

так какmodule.exportБраузеры не поддерживают его, поэтому webpack изменяет код на то, что браузеры могут распознать. теперь будетindex.htmlФайл открывается в браузере, также должен быть виден правильный лог.

Мы установили webpack в папку раньше, и каждый раз мы должны входитьnode_modules/.bin/webpackслишком громоздко, можноpackage.jsonИзменить следующим образом

"scripts": {
    "start": "webpack"
  },

затем выполнить сноваnpm run start, можно обнаружить, что эффект такой же, как и раньше. Пока что для простоты использования давайте рассмотрим больше возможностей webpack.

Loader

Loader — очень мощная функция webpack, эта функция позволяет использовать множество новых технологий.

Babel

Babel позволяет вам использовать ES2015/16/17 для написания кода, не беспокоясь о проблемах с браузером, Babel может помочь вам преобразовать ваш код. Сначала установите необходимые библиотеки Babel

npm i --save-dev babel-loader babel-core babel-preset-env

Давайте сначала представим три библиотеки, которые мы установили.

  • babel-loader используется для того, чтобы webpack знал, как запускать babel
  • babel-core можно рассматривать как компилятор, эта библиотека умеет парсить код
  • babel-preset-env Эта библиотека может преобразовывать код в соответствии с различными средами.

следующее изменениеwebpack-config.jsкод в

module.exports = {
// ......
    module: {
        rules: [
            {
            // js 文件才使用 babel
                test: /\.js$/,
             // 使用哪个 loader
                use: 'babel-loader',
            // 不包括路径
                exclude: /node_modules/
            }
        ]
    }
}

Есть много способов настроить Babel, и рекомендуется использовать управление файлами .babelrc.

// ..babelrc
{
    "presets": ["babel-preset-env"]
}

Теперь измените предыдущий код JS на запись ES6.

// sum.js
export default (a, b) => {
    return a + b
}
// index.js
import sum from './sum'
console.log(sum(1, 2))

воплощать в жизньnpm run start, а затем наблюдатьbundle.jsВ коде можно обнаружить, что код был преобразован, а также может нормально выводить 3.

Конечно, Babel — это гораздо больше, чем эти функции, и те, кто заинтересован, могут зайти на официальный сайт, чтобы изучить их самостоятельно.

обрабатывать изображения

В этом разделе мы будем использоватьurl-loaderиfile-loader, эти две библиотеки умеют не только обрабатывать картинки, но и имеют другие функции, а кому интересно, могут научиться сами.

Сначала установите библиотеку

npm i --save-dev url-loader file-loader

Создаватьimagesпапку, поместите две картинки иappСоздайте файл js в папке для обработки изображений , текущая структура папок показана на рисунке

// addImage.js
let smallImg = document.createElement('img')
// 必须 require 进来
smallImg.src = require('../images/small.jpeg')
document.body.appendChild(smallImg)

let bigImg = document.createElement('img')
bigImg.src = require('../images/big.jpeg')
document.body.appendChild(bigImg)

Изменить следующийwebpack.config.jsкод

module.exports = {
// ...
    module: {
        rules: [
            // ...
            {
            // 图片格式正则
                test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
                use: [
                  {
                    loader: 'url-loader',
                    // 配置 url-loader 的可选项
                    options: {
                    // 限制 图片大小 10000B,小于限制会将图片转换为 base64格式
                      limit: 10000,
                    // 超出限制,创建的文件格式
                    // build/images/[图片名].[hash].[图片格式]
                      name: 'images/[name].[hash].[ext]'
                   }
                  }
                ]
            }
        ]
    }
  }

бегатьnpm run start, упаковка успешна, как показано ниже

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

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

module.exports = {
    entry:  './app/index.js', // 入口文件
    output: {
      path: path.resolve(__dirname, 'build'), // 必须使用绝对地址,输出文件夹
      filename: "bundle.js", // 打包后输出文件的文件名
      publicPath: 'build/' // 知道如何寻找资源
    }
    // ...
  }

Наконец запуститьnpm run start, компиляция прошла успешно, снова обновите следующую страницу, вы можете обнаружить, что на этот раз большая картинка отображается правильно. В следующем подразделе мы опишем, как работать с файлами CSS.

Работа с файлами CSS.

Добавить кstylesпапка, новаяaddImage.cssфайл, а затем добавить код в этот файл

img {
    border: 5px black solid;
}
.test {border: 5px black solid;}

В этом разделе мы сначала используемcss-loaderиstyle-loaderбиблиотека. Первый позволяет файлам CSS также поддерживатьimpost, и будет анализировать файл CSS, последний может вставить проанализированный CSS в HTML в виде тегов, поэтому последний зависит от первого.

npm i --save-dev css-loader style-loader

Сначала изменитьaddImage.jsдокумент

import '../styles/addImage.css'

let smallImg = document.createElement('img')
smallImg.src = require('../images/small.jpeg')
document.body.appendChild(smallImg)

// let bigImg = document.createElement('img')
// bigImg.src = require('../images/big.jpeg')
// document.body.appendChild(bigImg)

затем изменитьwebpack.config.jsкод

module.exports = {
// ...
    module: {
      rules: [
        {
            test: /\.css$/,
            use: ['style-loader',
                {
                    loader: 'css-loader',
                    options: {
                        modules: true
                       }
                }
            ]
        },
      ]
    }
  }

спуститьсяnpm run start, а затем обновите страницу, вы обнаружите, что картинка правильно оформлена, теперь давайте посмотрим на структуру файла HTML

Как видно из рисунка выше, мыaddImage.cssКод, записанный в файле, добавляется вstyleтег, и потому что мы включили опцию модульности CSS, поэтому.testпреобразуется в уникальное хеш-значение, что решает проблему повторяющихся имен переменных в CSS.

Однако интеграция CSS-кода в JS-файлы также имеет недостатки: большое количество CSS-кода приведет к увеличению размера JS-файла, а работа с DOM также вызовет проблемы с производительностью, поэтому мы будем использовать его далее.extract-text-webpack-pluginПлагины упаковывают файлы CSS в один файл

Установить первымnpm i --save-dev extract-text-webpack-plugin

затем изменитьwebpack.config.jsкод

const ExtractTextPlugin = require("extract-text-webpack-plugin")

module.exports = {
// ....
    module: {
      rules: [
        {
          test: /\.css$/,
          // 写法和之前基本一致
          loader: ExtractTextPlugin.extract({
          // 必须这样写,否则会报错
                fallback: 'style-loader',
                use: [{
                    loader: 'css-loader',
                    options: { 
                        modules: true
                    }
                }]
            })
        ]
        }
      ]
    },
    // 插件列表
    plugins: [
    // 输出的文件路径
      new ExtractTextPlugin("css/[name].[hash].css")
    ]
  }

спуститьсяnpm run start, вы можете обнаружить, что файлы CSS упакованы отдельно

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

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

Пожалуйста, последовательно следуйте приведенному ниже коду

git clone https://github.com/KieSun/webpack-demo.git
cd webpack-demo
// 切换到 0.1 标签上并创建一个新分支
git checkout -b demo 0.1
cd project
npm i 
// 查看分支是否为 demo,没问题的话就可以进行下一步

Как использовать webpack в своем проекте

В проекте настроены очень простые babel и webpack, которые запускаются напрямую.npm run buildПросто

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

отдельный код

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

// 这是 packet.json 中 dependencies 下的
const VENOR = ["faker",
  "lodash",
  "react",
  "react-dom",
  "react-input-range",
  "react-redux",
  "redux",
  "redux-form",
  "redux-thunk"
]

module.exports = {
// 之前我们都是使用了单文件入口
// entry 同时也支持多文件入口,现在我们有两个入口
// 一个是我们自己的代码,一个是依赖库的代码
  entry: {
  // bundle 和 vendor 都是自己随便取名的,会映射到 [name] 中
    bundle: './src/index.js',
    vendor: VENOR
  },
  output: {
    path: path.join(__dirname, 'dist'),
    filename: '[name].js'
  },
  // ...
 }

Теперь давайте построим его и посмотрим, появятся ли какие-нибудь сюрпризы

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

Извлечь общий код

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

module.exports = {
//...
  output: {
    path: path.join(__dirname, 'dist'),
    // 既然我们希望缓存生效,就应该每次在更改代码以后修改文件名
    // [chunkhash]会自动根据文件是否更改而更换哈希
    filename: '[name].[chunkhash].js'
  },
  plugins: [
    new webpack.optimize.CommonsChunkPlugin({
    // vendor 的意义和之前相同
    // manifest文件是将每次打包都会更改的东西单独提取出来,保证没有更改的代码无需重新打包,这样可以加快打包速度
      names: ['vendor', 'manifest'],
      // 配合 manifest 文件使用
      minChunks: Infinity
    })
  ]
};

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

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

npm install --save-dev clean-webpack-plugin

Затем измените файл конфигурации

module.exports = {
//...
  plugins: [
  // 只删除 dist 文件夹下的 bundle 和 manifest 文件
    new CleanWebpackPlugin(['dist/bundle.*.js','dist/manifest.*.js'], {
    // 打印 log
      verbose: true,
      // 删除文件
      dry: false
    }),
  ]
};

Затем при сборке вы обнаружите, что вышеуказанные файлы удалены.

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

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

Затем измените файл конфигурации

module.exports = {
//...
  plugins: [
  // 我们这里将之前的 HTML 文件当做模板
  // 注意在之前 HTML 文件中请务必删除之前引入的 JS 文件
    new HtmlWebpackPlugin({
      template: 'index.html'
    })
  ]
};

Выполните операцию сборки, и вы обнаружите, что файлы HTML генерируются одновременно, а файлы JS автоматически импортируются.

Загружать код по запросу

В этом разделе мы узнаем, как загружать код по запросу. Я обнаружил, что забыл добавить библиотеку маршрутизатора в запись поставщика раньше. Вы можете добавить эту библиотеку и перестроить ее, и вы обнаружите, что пакет меньше 300 КБ. .

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

Изменить сейчасsrc/router.js

// 注意在最新版的 V4路由版本中,更改了按需加载的方式,如果安装了 V4版,可以自行前往官网学习
import React from 'react';
import { Router, Route, IndexRoute, hashHistory } from 'react-router';

import Home from './components/Home';
import ArtistMain from './components/artists/ArtistMain';

const rootRoute = {
  component: Home,
  path: '/',
  indexRoute: { component: ArtistMain },
  childRoutes: [
    {
      path: 'artists/new',
      getComponent(location, cb) {
        System.import('./components/artists/ArtistCreate')
          .then(module => cb(null, module.default))
      }
    },
    {
      path: 'artists/:id/edit',
      getComponent(location, cb) {
        System.import('./components/artists/ArtistEdit')
          .then(module => cb(null, module.default))
      }
    },
    {
      path: 'artists/:id',
      getComponent(location, cb) {
        System.import('./components/artists/ArtistDetail')
          .then(module => cb(null, module.default))
      }
    }
  ]
}

const Routes = () => {
  return (
    <Router history={hashHistory} routes={rootRoute} />
  );
};

export default Routes;

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

Откройте файл HTML в браузере.Когда вы нажмете на переход по маршруту, вы увидите, что файл JS загружается в столбце «Сеть» инструментов разработчика.

титульная страница

После нажатия Random Artist в правом верхнем углу

Автоматическое обновление

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

Сначала установите плагин

npm i --save-dev webpack-dev-server

Затем измените файл package.json.

"scripts": {
    "build": "webpack",
    "dev": "webpack-dev-server --open"
  },

Теперь выполните напрямуюnpm run devМожно обнаружить, что браузер автоматически открывает пустую страницу, а в командной строке появляется новый вывод

Дождавшись завершения компиляции, измените файл JS или CSS, вы можете обнаружить, что webpack автоматически завершает компиляцию для нас и обновляет только тот код, который необходимо обновить.

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

Сгенерировать производственный код

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

npm i --save-dev url-loader optimize-css-assets-webpack-plugin file-loader extract-text-webpack-plugin

Изменить конфигурацию веб-пакета

var webpack = require('webpack');
var path = require('path');
var HtmlWebpackPlugin = require('html-webpack-plugin')
var CleanWebpackPlugin = require('clean-webpack-plugin')
var ExtractTextPlugin = require('extract-text-webpack-plugin')
var OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')

const VENOR = ["faker",
  "lodash",
  "react",
  "react-dom",
  "react-input-range",
  "react-redux",
  "redux",
  "redux-form",
  "redux-thunk",
  "react-router"
]

module.exports = {
  entry: {
    bundle: './src/index.js',
    vendor: VENOR
  },
  // 如果想修改 webpack-dev-server 配置,在这个对象里面修改
  devServer: {
    port: 8081
  },
  output: {
    path: path.join(__dirname, 'dist'),
    filename: '[name].[chunkhash].js'
  },
  module: {
    rules: [{
        test: /\.js$/,
        use: 'babel-loader'
      },
      {
        test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
        use: [{
            loader: 'url-loader',
            options: {
                limit: 10000,
                name: 'images/[name].[hash:7].[ext]'
            }
        }]
    },
    {
        test: /\.css$/,
        loader: ExtractTextPlugin.extract({
            fallback: 'style-loader',
            use: [{
            // 这边其实还可以使用 postcss 先处理下 CSS 代码
                loader: 'css-loader'
            }]
        })
    },
    ]
  },
  plugins: [
    new webpack.optimize.CommonsChunkPlugin({
      name: ['vendor', 'manifest'],
      minChunks: Infinity
    }),
    new CleanWebpackPlugin(['dist/*.js'], {
      verbose: true,
      dry: false
    }),
    new HtmlWebpackPlugin({
      template: 'index.html'
    }),
    // 生成全局变量
    new webpack.DefinePlugin({
      "process.env.NODE_ENV": JSON.stringify("process.env.NODE_ENV")
    }),
    // 分离 CSS 代码
    new ExtractTextPlugin("css/[name].[contenthash].css"),
    // 压缩提取出的 CSS,并解决ExtractTextPlugin分离出的 JS 重复问题
    new OptimizeCSSPlugin({
      cssProcessorOptions: {
        safe: true
      }
    }),
    // 压缩 JS 代码
    new webpack.optimize.UglifyJsPlugin({
      compress: {
        warnings: false
      }
    })
  ]
};

Измените файл package.json.

"scripts": {
    "build": "NODE_ENV=production webpack -p",
    "dev": "webpack-dev-server --open"
  }

воплощать в жизньnpm run build

Видно, что после стольких шагов мы уменьшили размер пакета до 27,1 КБ. Обычно мы можем использовать CDN для ссылки на общие библиотеки, такие как поставщик.

Пополнить

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

module.exports = {
  resolve: {
  // 文件扩展名,写明以后就不需要每个文件写后缀
    extensions: ['.js', '.css', '.json'],
 // 路径别名,比如这里可以使用 css 指向 static/css 路径
    alias: {
      '@': resolve('src'),
      'css': resolve('static/css')
    }
  },
  // 生成 source-map,用于打断点,这里有好几个选项
  devtool: '#cheap-module-eval-source-map',
}

постскриптум

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

Статья длинная, и ошибки неизбежны.Если вы обнаружите какие-либо проблемы или что-то, что я не понимаю, вы можете оставить мне сообщение.