Webpack actual combat

Webpack actual combat

To view all document pages:Full stack developmentFor more information.

After working overtime, I finally sorted out this document. By the way, I studied deeply and consolidated my knowledge, which was too tiring and affected the sleep time and quality. Geeks just want to get things done to the extreme. They must reach the end when they start.

Original link:Webpack actual combat, the original advertising mode box block, reading experience is not good, so organize the text, easy to find.

This chapter teaches you how to use Webpack to solve common scenarios in actual projects.

According to different scenarios, it is divided into the following categories:

  • Use new languages to develop projects:

    • Use ES6 language
    • Use TypeScript language
    • Use the Flow inspector
    • Use SCSS language
    • Use PostCSS
  • Use the new framework to develop projects:

    • Use React framework
    • Use Vue framework
    • Use Angular2 framework
  • Building a single page application with Webpack:

    • Generate HTML for Single Page Applications
    • Manage multiple single-page applications
  • Building Projects in Different Operating Environments with Webpack:

    • Constructing Isomorphic Applications
    • Building Electron applications
    • Building Npm module
    • Building Offline Applications
  • The Webpack is used in combination with other tools to draw on each other’s strengths:

    • With Npm Script
    • Check code
    • Jsapi to start Webpack
    • Using Webpack Dev Middleware
  • Loading special types of resources with Webpack:

    • Load picture
    • Load SVG
    • Load Source Map

Use TypeScript language

Since TypeScript is not recommended in this article, ES6 is sufficient for most tasks. Original link:Use TypeScript language

Use Angular2 framework

Angular2 is not within the scope of my technology stack, so this chapter is not included. Please check the original text if you are interested:Use Angular2 framework

Use ES6 language

Usually, we need to convert the code written with ES6 into ES5 code that already supports well. This includes two things:

  1. Implement the new ES6 syntax with ES5, such as ES6classThe grammar uses ES5prototypeImplementation.
  2. Inject polyfill into new apis, e.g. use new ones.fetchOnly after the corresponding polyfill is injected into the API can the low-end browser run normally.

Babel

Babel can easily accomplish the above two things.

Babel is a JavaScript compiler that can convert ES6 code into ES5 code, allowing you to use the latest language features without worrying about compatibility issues, and can be flexibly extended according to requirements through plug-in mechanisms.

During Babel’s compilation, the.babelrcFile read configuration..babelrcIs a JSON format file, the content is roughly as follows:

{
  "plugins": [
    [
      "transform-runtime",
      {
        "polyfill": false
      }
    ]
   ],
  "presets": [
    [
      "es2015",
      {
        "modules": false
      }
    ],
    "stage-2",
    "react"
  ]
}

Plugins

pluginsProperty tells Babel which plug-ins to use and which plug-ins can control how code is converted.

In the above configuration filetransform-runtimeThe full name of the corresponding plug-in is calledbabel-plugin-transform-runtime, that is, in front of the addedbabel-plugin-For Babel to function properly, we must first install it:

npm i -D babel-plugin-transform-runtime

babel-plugin-transform-runtimeBabel is an official plug-in provided by Babel to reduce redundant codes.

Babel usually needs some auxiliary functions written by ES5 to complete the implementation of the new syntax when converting ES6 code into ES5 code, for example, when convertingclass extentSyntax will be injected into the converted ES5 code_extentAuxiliary functions are used to implement inheritance:

function _extent(target) {
  for (var i = 1; i < arguments.length; i++) {
    var source = arguments[i];
    for (var key in source) {
      if (Object.prototype.hasOwnProperty.call(source, key)) {
        target[key] = source[key];
      }
    }
  }
  return target;
}

This will result in each usedclass extentSyntax files are injected with duplicates_extentAuxiliary function code,babel-plugin-transform-runtimeThe function of the is not to inject the auxiliary function content into the file, but to inject an import statement:

var _extent = require('babel-runtime/helpers/_extent');

This can reduce the file size of Babel’s compiled code.

At the same time, it should be noted that due tobabel-plugin-transform-runtimeInjectedrequire('babel-runtime/helpers/_extent')Statement to the compiled code, need to installbabel-runtimeThe code will not work properly until it depends on your project. That is to saybabel-plugin-transform-runtimeAndbabel-runtimeNeed to be used together, usedbabel-plugin-transform-runtimeAfter must needbabel-runtime.

Presets

presetsProperty tells Babel which new syntax features are used in the source code to be converted, onePresetsSupport for a new set of syntax features, multiplePresetsCan be superimposed.

PresetsIn fact, it is a collection of Plugins, and each Plugin completes the conversion of a new grammar. Presets are organized according to the ECMAScript draft and can be generally divided into the following three categories:

  1. Features that have been written into ECMAScript standard are added to the standard every year before.

    • Env contains the latest features in all current ECMAScript standards.
  2. The features proposed by the community but not yet written into ECMAScript standard can be divided into the following four types:

    • stage0It is just a beautiful and radical idea. Babel plug-in supports these features, but it is not sure whether it will be set as a standard.
    • stage1Features worthy of inclusion in the standard;
    • stage2This specification has been drafted and will be incorporated into the standard.
    • stage3The specification of this feature has been finalized, and major browser manufacturers and Node.js community have begun to implement it.
    • stage4It will be added to the standard in the following year.
  3. In order to support syntax in some specific application scenarios, it has nothing to do with ECMAScript standard, for examplebabel-preset-reactIt is to support JSX syntax in React development.

In practical application, you need to install the corresponding Plugins or Presets according to the syntax used in the source code of the project.

Access Babel

Since Babel is converting code, it should access Babel through Loader. The Webpack is configured as follows:

module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        use: ['babel-loader'],
      },
    ]
  },
  // 输出 source-map 方便直接调试 ES6 源码
  devtool: 'source-map'
};

The configuration hit all JavaScript files in the project directory and passedbabel-loaderTo call Babel to complete the conversion. The newly introduced dependencies need to be installed before re-executing the build:

# Webpack 接入 Babel 必须依赖的模块
npm i -D babel-core babel-loader 
# 根据你的需求选择不同的 Plugins 或 Presets
npm i -D babel-preset-env

Use SCSS language

SCSS allows you to write CSS in a more flexible way. It is a CSS preprocessor with syntax similar to CSS, but with the addition of programming elements such as variables and logic, the code is similar to this:

$blue: #1875e7; 

div {
  color: $blue;
}

SCSS is also called SASS. The difference is that SASS syntax is similar to Ruby, while SCSS syntax is similar to CSS. For front-end engineers who are familiar with CSS, they prefer SCSS.

The advantage of using SCSS to write CSS is that it can easily manage code, separate common parts, and write more flexible code through logic. CSS preprocessors similar to SCSS include LESS, etc.

Using SCSS can improve coding efficiency, but the SCSS source code must be compiled into CSS code that can run directly in the browser environment.

node-sassThe core module is written by C++, and is encapsulated with Node.js to provide other Node.js calls.node-sassIt also supports calling from the command line to install it to the global first:

npm i -g node-sass

Then execute the compile command:


# 把 main.scss 源文件编译成 main.css
node-sass main.scss main.css
    

You can see the compiled source code in the same directory.main.cssDocuments.

Access Webpack

Webpack accesssass-loaderThe relevant configuration is as follows:

module.exports = {
  module: {
    rules: [
      {
        // 增加对 SCSS 文件的支持
        test: /\.scss/,
        // SCSS 文件的处理顺序为先 sass-loader 再 css-loader 再 style-loader
        use: ['style-loader', 'css-loader', 'sass-loader'],
      },
    ]
  },
};

The above configuration is regular/\.scss/Match all to.scssSCSS files with suffix shall be processed by 3 Loader respectively. The specific processing flow is as follows:

  1. viasass-loaderTurn the SCSS source code into CSS code, and then hand over the CSS code tocss-loaderTo deal with.
  2. css-loaderWill find the@importAndurl()This import statement tells Webpack to rely on these resources. At the same time, it also supports CSS Modules, compression CSS and other functions. Give the results to after processingstyle-loaderTo deal with.
  3. style-loaderAfter converting CSS code into strings, it is injected into JavaScript code to add styles to DOM through JavaScript. If you want to extract CSS code into a separate file instead of mixing it with JavaScript, you can use 1-5 to use the extratextPlugin introduced in plugin.

Due to accesssass-loader, the project needs to install these new dependencies:

# 安装 Webpack Loader 依赖
npm i -D  sass-loader css-loader style-loader
# sass-loader 依赖 node-sass
npm i -D node-sass
    

Use the Flow inspector

Flow is an open source JavaScript static type detector for Facebook, which is a superset of JavaScript language.

What you need to do is to add type checking where it is needed, for example, to add static type checking to the interface of two modules developed by different people, which can point out the improper use of some modules in the compilation stage. At the same time, Flow can also check out potential bugs in JavaScript code through type inference.

Flow has the following effects:

// @flow

// 静态类型检查
function square1(n: number): number {
  return n * n;
}
square1('2'); // Error: square1 需要传入 number 作为参数

// 类型推断检查
function square2(n) {
  return n * n; // Error: 传入的 string 类型不能做乘法运算
}
square2('2');


The first line in the code when you need to pay attention// @flowTell the Flow Inspector that this file needs to be checked.

Use Flow

Flow detector is written by high-performance cross-platform OCaml language, and its executable file can be passed through:

npm i -D flow-bin

After installation, configure Npm Script first:

"scripts": {
   "flow": "flow"
}

Pass againnpm run flowTo call Flow to perform code checks.

In addition, you can also pass:

npm i -g flow-bin

After the Flow is installed to the global, it passes through directly.flowCommand to perform code checks.

After the installation is successful, after the Flow is executed under the root directory of the project, the Flow will traverse all files that need to be checked, check them, and output the error result to the console.

JavaScript using Flow static type syntax cannot be directly run in the existing JavaScript engine. to make the code run, these static type syntax should be removed.

// 采用 Flow 的源代码
function foo(one: any, two: number, three?): string {}

// 去掉静态类型语法后输出代码
function foo(one, two, three) {}

There are two ways to do this:

  1. flow-remove-typesCan be used alone with high speed.
  2. babel-preset-flowIntegration with Babel.

Integrated Webpack

Because ES6 syntax is commonly used in projects that use Flow, the most convenient way to integrate Flow into projects built with Webpack is through Babel.

  1. Installationnpm i -D babel-preset-flowDepends on the project.
  2. Modify.babelrcConfiguration file, add flowpresent:

    "presets": [
    ...[],
    "flow"
    ]

After adding static types to the source code and rebuilding the project, you will find that the source code using Flow can still run normally in the browser.

The purpose of explicit construction is only to remove the Flow static type syntax from the source code, and code checking has nothing to do with construction. Many editors have integrated Flow and can highlight problems detected by Flow in code in real time.

Use PostCSS

PostCSS is a CSS processing tool, which is different from SCSS in that it can flexibly extend its supported features through plug-in mechanism, rather than the syntax is fixed like SCSS. PostCSS has many uses, including automatically prefixed CSS and using the next generation of CSS syntax. At present, more and more people begin to use it. It is likely to become the ultimate winner of CSS preprocessor.

PostCSS and CSS are just like Babel and JavaScript. They release the restriction of grammar, extend the language itself through plug-in mechanism, and bring more possibilities to the language by engineering means.

The relationship between PostCSS and SCSS is just like the relationship between Babel and TypeScript. PostCSS is more flexible and expandable, while SCSS has many built-in functions and cannot be expanded.

Automatically prefix CSS to increase compatibility of browsers:

/*输入*/
h1 {
  display: flex;
}

/*输出*/
h1 {
  display: -webkit-box;
  display: -webkit-flex;
  display: -ms-flexbox;
  display: flex;
}

Using Next Generation CSS Syntax:

/*输入*/
:root {
  --red: #d33;
}

h1 {
  color: var(--red);
}


/*输出*/
h1 { 
  color: #d33;
}

PostCSS are all written in JavaScript and run on Node.js, which provides modules for JavaScript code calls and executable files.

When PostCSS starts, thepostcss.config.jsThe required configuration is read from the file, so the file needs to be newly created. The contents of the file are roughly as follows:

module.exports = {
  plugins: [
    // 需要使用的插件列表
    require('postcss-cssnext')
  ]
}

thereinpostcss-cssnextThe plug-in allows you to write code using the next generation of CSS syntax, and then convert it into CSS recognizable by the current browser through PostCSS. The plug-in also includes the function of automatically prefix CSS.

At present, modern browsers such as Chrome can already fully support it.cssnextIn other words, according tocssnextSyntactic CSS can be run directly in the browser without conversion.

Access Webpack

Although the PostCSS suffix is still used.cssBut these documents must be handed in first.postcss-loaderHandle it again and then hand it overcss-loader.

The Webpack configuration related to accessing PostCSS is as follows:

module.exports = {
  module: {
    rules: [
      {
        // 使用 PostCSS 处理 CSS 文件
        test: /\.css/,
        use: ['style-loader', 'css-loader', 'postcss-loader'],
      },
    ]
  },
};

Accessing PostCSS brings new dependencies to the project and requires installation, as follows:

# 安装 Webpack Loader 依赖
npm i -D postcss-loader css-loader style-loader
# 根据你使用的特性安装对应的 PostCSS 插件依赖
npm i -D postcss-cssnext
    

Use React framework

React Grammatical Features

Code features that use React projects include JSX and Class syntax, such as:

class Button extends Component {
  render() {
    return <h1>Hello,Webpack</h1>
  }
}   

JSX and Class syntax are not required in projects that use React, but code written using the new syntax looks more elegant.

The JSX syntax cannot be run in any existing JavaScript engine, so the source code needs to be converted into executable code during the construction process, for example:

// 原 JSX 语法代码
return <h1>Hello,Webpack</h1>

// 被转换成正常的 JavaScript 代码
return React.createElement('h1', null, 'Hello,Webpack')

React and Babel

It is very simple to connect React framework to projects using Babel, only need to add Presets on which React depends.babel-preset-react.

Pass the following command:

# 安装 React 基础依赖
npm i -D react react-dom
# 安装 babel 完成语法转换所需依赖
npm i -D babel-preset-react

After installing the new dependency, modify it.babelrcConfiguration file added to React Presets

"presets": [
    "react"
],

All the preparations have been completed.

Revise againmain.jsThe documents are as follows:

import * as React from 'react';
import { Component } from 'react';
import { render } from 'react-dom';

class Button extends Component {
  render() {
    return <h1>Hello,Webpack</h1>
  }
}

render(<Button/>, window.document.getElementById('app'));

Re-execute the build to open the web page and you will find the rendered by ReactHello,Webpack.

React and TypeScript

The advantage of TypeScript over Babel is that it supports JSX syntax natively. You don’t need to reinstall new dependencies, you just need to modify one line of configuration. But TypeScript differs in that:

  • The file suffix using JSX syntax must betsx.
  • Since React is not written by TypeScript, it needs to be installed.reactAndreact-domCorresponding TypeScript Interface Description Module@types/reactAnd@types/react-domBefore it can be compiled.

Modify TypeScript Compiler Profiletsconfig.jsonIncrease support for JSX syntax as follows:

{
  "compilerOptions": {
    "jsx": "react" // 开启 jsx ,支持 React
  }
}

Due tomain.jsJSX syntax exists in the file, and thenmain.jsRename the file tomain.tsxAt the same time, modify the contents of the file as React code used in React and Babel above. At the same time, in order for the Webpack to be able totsAndtsxThe original documents are all adopted.awesome-typescript-loaderTo convert, it should be noted that the Webpack Loader is configuredtestOptions need to match totsxType of file, andextensionsAlso add.tsx, configured as follows:

module.exports = {
  // TS 执行入口文件
  entry: './main',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, './dist'),
  },
  resolve: {
    // 先尝试 ts,tsx 后缀的 TypeScript 源码文件 
    extensions: ['.ts', '.tsx', '.js',] 
  },
  module: {
    rules: [
      {
        // 同时匹配 ts,tsx 后缀的 TypeScript 源码文件 
        test: /\.tsx?$/,
        loader: 'awesome-typescript-loader'
      }
    ]
  },
  devtool: 'source-map',// 输出 Source Map 方便在浏览器里调试 TypeScript 代码
};

vianpm i react react-dom @types/react @types/react-domAfter installing the new dependency, restart the build, reopen the web page, and you will find the rendering by ReactHello,Webpack.

Use Vue framework

Vue is a progressive MVVM framework, which is more flexible and lightweight than React and Angular. It doesn’t have some mandatory built-in functions and syntax, you can add functions bit by bit according to your needs. Although Vue-based projects can be written in code that can be directly run in a browser environment, most projects will be written in Vue’s official single-file component method to facilitate coding.

Vue’s single file component passes through an HTML-like.vueThe document can clearly describe the template, style and logic required by a component.

main.jsEntry file:

import Vue from 'vue'
import App from './App.vue'

new Vue({
  el: '#app',
  render: h => h(App)
});

The entry file creates a root instance of Vue with IDappThe App component defined above is rendered on the DOM node of.

Access Webpack

At present, the most mature and popular way to develop Vue projects is to use ES6 plus Babel transformation, which is very similar to the basic projects developed with ES6, and the difference lies in analysis..vueSingle file component in format. Fortunately, Vue officials provided the correspondingvue-loaderThe conversion of single file components can be completed very conveniently.

Modify the Webpack related configuration as follows:

module: {
  rules: [
    {
      test: /\.vue$/,
      use: ['vue-loader'],
    },
  ]
}

Install newly introduced dependencies:

# Vue 框架运行需要的库
npm i -S vue
# 构建所需的依赖
npm i -D vue-loader css-loader vue-template-compiler

In these dependencies, their roles are:

  • vue-loader: parsing and conversion.vueFile, extract the logic codescript, style codestyleAnd HTML templatestemplate, and then respectively to the corresponding Loader to deal with them.
  • css-loader: load byvue-loaderThe extracted CSS code.
  • vue-template-compiler: handlevue-loaderThe extracted HTML template is compiled into corresponding executable JavaScript code, which is similar to JSX syntax in React being compiled into JavaScript code. The advantage of compiling HTML templates in advance over compiling HTML templates in browsers is better performance.

Writing Vue applications using TypeScript

Starting from Vue version 2.5.0+,it provides good support for TypeScript. Writing VUE using TypeScript is a good choice because TypeScript can check out some potential errors.

Newtsconfig.jsonThe configuration file reads as follows:

{
  "compilerOptions": {
    // 构建出 ES5 版本的 JavaScript,与 Vue 的浏览器支持保持一致
    "target": "es5",
    // 开启严格模式,这可以对 `this` 上的数据属性进行更严格的推断
    "strict": true,
    // TypeScript 编译器输出的 JavaScript 采用 es2015 模块化,使 Tree Shaking 生效
    "module": "es2015",
    "moduleResolution": "node"
  }
}

ModifyApp.vueThe script section reads as follows:

<!--组件逻辑-->
<script lang="ts">
  import Vue from "vue";

  // 通过 Vue.extend 启用 TypeScript 类型推断
  export default Vue.extend({
    data() {
      return {
        msg: 'Hello,Webpack',
      }
    },
  });
</script>

pay attention toscriptIn the labellang="ts"To indicate that the syntax of the code is TypeScript.

Modify the main.ts execution entry file to read as follows:

import Vue from 'vue'
import App from './App.vue'

new Vue({
  el: '#app',
  render: h => h(App)
});

Because TypeScript does not know.vueThe end of the file, in order to make its supportimport App from './App.vue'To import a statement, you also need the following filesvue-shims.d.tsTo define.vueType of:

// 告诉 TypeScript 编译器 .vue 文件其实是一个 Vue  
declare module "*.vue" {
  import Vue from "vue";
  export default Vue;
}

The Webpack configuration needs to be modified in two places, as follows:

const path = require('path');

module.exports = {
  resolve: {
    // 增加对 TypeScript 的 .ts 和 .vue 文件的支持
    extensions: ['.ts', '.js', '.vue', '.json'],
  },
  module: {
    rules: [
      // 加载 .ts 文件
      {
        test: /\.ts$/,
        loader: 'ts-loader',
        exclude: /node_modules/,
        options: {
          // 让 tsc 把 vue 文件当成一个 TypeScript 模块去处理,以解决 moudle not found 的问题,tsc 本身不会处理 .vue 结尾的文件
          appendTsSuffixTo: [/\.vue$/],
        }
      },
    ]
  },
};

In addition, newly introduced dependencies need to be installed:npm i -D ts-loader typescript

Generate HTML for Single Page Applications

Introduction problem

In using React framework, it is the simplestHello,WebpackAs an example, let us understand that in this example, only one was output.bundle.jsFile, so hand-written a.index.htmlFile to introduce thisbundle.jsIn order for the application to run in the browser.

It is much more complicated than this in actual projects. A page often has a lot of resources to load. The following is an actual combat example, which requires the following:

  1. The project uses ES6 language plus React framework.
  2. To add Google Analytics to the page, this part of the code needs to be embedded in the HEAD tag.
  3. Add Disqus user comments to the page. This part of code needs to be loaded asynchronously to improve the loading speed of the first screen.
  4. Compress and separate JavaScript and CSS code to improve loading speed.

Before starting, let’s take a look at the application that was finally released online.Code.

It can be seen that some codes are embedded into the HEAD tag of HTML, the file names of some files are marked with Hash values calculated according to the contents of the files, and the URL addresses for loading these files are also normally injected into HTML.

Solution

Recommend a Webpack plug-in to easily solve the above problemsweb-webpack-plugin. This plug-in has been used and verified by many people in the community, which has solved everyone’s pain points and won a lot of favorable comments. Here’s how to use it to solve the above problems.

First of all, modifyWebpack configuration.

Most of the above configurations are those that have been added according to the contents already mentioned, for example:

  • Increase support for CSS files, extract CSS codes from Chunk into separate files, and compress CSS files;
  • DefinitionNODE_ENVThe environmental variables areproductionIn order to remove the part of the source code that is only needed for development;
  • Add Hash value to the output file name;
  • Compressed output JavaScript code.

But the core part ispluginsFrom:

new WebPlugin({
  template: './template.html', // HTML 模版文件所在的文件路径
  filename: 'index.html' // 输出的 HTML 的文件名称
})

among themtemplate: './template.html'The template file referred totemplate.htmlThe content of the is:

<head>
  <meta charset="UTF-8">
  <!--注入 Chunk app 中的 CSS-->
  <link rel="stylesheet" href="app?_inline">
  <!--注入 google_analytics 中的 JavaScript 代码-->
  <script src="./google_analytics.js?_inline"></script>
  <!--异步加载 Disqus 评论-->
  <script src="https://dive-into-webpack.disqus.com/embed.js" async></script>
</head>
<body>
<div id="app"></div>
<!--导入 Chunk app 中的 JS-->
<script src="app"></script>
<!--Disqus 评论容器-->
<div id="disqus_thread"></div>
</body>

This file describes which resources need to be added to the output HTML file in which way.

In order to<link rel="stylesheet" href="app? _inline">For example, code produced by Webpack is introduced according to the same syntax as CSS files.hrefThe in the propertyapp? _inlineCan be divided into two parts, the frontappIndicates that CSS code comes from a source namedappOf the Chunk, the following_inlineIndicates that these codes need to be embedded in the location of this tag.

The same<script src="./google_analytics.js? _inline"></script>Indicates that JavaScript code is from a file relative to the current templatetemplate.htmlLocal files for./google_analytics.jsAnd the JavaScript code in the file needs to be embedded in the location of this tag.

In other words, the part in front of the question mark in the URL string of the resource link indicates where the content of the resource comes from and the part behind it.querystringIndicates how these resources are injected.

Except .._inlineIndicates that in addition to embedding, the following attributes are supported:

  • _distThis resource is introduced only in a production environment.
  • _devThis resource is introduced only in the development environment.
  • _ieOnly IE browser needs to introduce resources through[if IE]>resource<! [endif]Annotation implementation.

These attributes can be used together without conflict. For exampleapp? _inline&_distIndicates that the resource is introduced only in a production environment and needs to be embedded in HTML.

WebPluginThe plug-in also supports some other more advanced usage, which can be accessed for detailsProject home pageRead the document.

Manage multiple single-page applications

Introduction problem

Before starting, let’s look at the code that the application finally released online.

<html>
<head>
<meta charset="UTF-8">
<!--从多个页面中抽离出的公共 CSS 代码-->
<link rel="stylesheet" href="common_7cc98ad0.css">
<!--只有这个页面需要的 CSS 代码-->
<link rel="stylesheet" href="login_e31e214b.css">
<!--注入 google_analytics 中的 JS 代码-->
<script>(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
    (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
    m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-XXXXX-Y', 'auto');
ga('send', 'pageview');</script>
<!--异步加载 Disqus 评论-->
<script async="" src="https://dive-into-webpack.disqus.com/embed.js"></script>
</head>
<body>
<div id="app"></div>
<!--从多个页面中抽离出的公共 JavaScript 代码-->
<script src="common_a1d9142f.js"></script>
<!--只有这个页面需要的 JavaScript 代码-->
<script src="login_f926c4e6.js"></script>
<!--Disqus 评论容器-->
<div id="disqus_thread"></div>
</body>
</html>

The directory structure constructed is as follows:

dist
├── common_029086ff.js
├── common_7cc98ad0.css
├── index.html
├── index_04c08fbf.css
├── index_b3d3761c.js
├── login.html
├── login_0a3feca9.js
└── login_e31e214b.css

If you follow the ideas in the previous section, you may need to configure each single page application with the following code:

new WebPlugin({
  template: './template.html', // HTML 模版文件所在的文件路径
  filename: 'login.html' // 输出的 HTML 的文件名称
})

And add the entry corresponding to the page to theenrtyIn the configuration item, it looks like this:

entry: {
  index: './pages/index/index.js',// 页面 index.html 的入口文件
  login: './pages/login/index.js',// 页面 login.html 的入口文件
}

When a new page is added, the Webpack configuration file needs to be modified and more than one section of code is newly inserted, which will make the built code difficult to maintain and error prone.

Solution

The project source directory structure is as follows:

├── pages
│   ├── index
│   │   ├── index.css // 该页面单独需要的 CSS 样式
│   │   └── index.js // 该页面的入口文件
│   └── login
│       ├── index.css
│       └── index.js
├── common.css // 所有页面都需要的公共 CSS 样式
├── google_analytics.js
├── template.html
└── webpack.config.js

The following requirements can be seen from the directory structure:

  • All code for single-page applications need to be placed in a directory, for examplepagesUnder the catalogue;
  • A single page applies a separate folder, such as the last generatedindex.htmlThe relevant codes are all inindexUnder the directory,login.htmlSimilarly;
  • There is one under the directory for each single-page application.index.jsThe file is used as the entry execution file.

AlthoughAutoWebPluginThe catalogue structure of the project part is mandatory, but judging from actual combat experience, this is an elegant catalogue specification, reasonably splitting up the codes, enabling new people to quickly understand the project structure and facilitating future maintenance.

The Webpack configuration file was modified as follows:

< pdata-height = “465” data-theme-id = “0” data-slug-hash = “gzjwb” data-default-tab = “js, result” data-user = “whjin” data-embedded-version = “2” data-pen-title = “webpack manages multiple single-page applications” class=”codepen”>See the PenWebpack manages multiple single-page applicationsby whjin (@whjin) onCodePen.</p>
<script async src=”https://static.codepen.io/ass …; ></script>

AutoWebPluginWill find outpages2 folders under the directoryindexAndlogin, the two folders as two single page application. And generates a Chunk configuration and a WebPlugin configuration for each single-page application respectively. The Chunk name applied to each single page is equal to the name of the folder, that is to sayautoWebPlugin.entry()The content returned by the method is actually:

{
  "index":["./pages/index/index.js","./common.css"],
  "login":["./pages/login/index.js","./common.css"]
}

But these thingsAutoWebPluginWill automatically complete for you, you don’t have to worry about, understand the general principle.

template.htmlThe template file is as follows:

<html>
<head>
  <meta charset="UTF-8">
  <!--在这注入该页面所依赖但没有手动导入的 CSS-->
  <!--STYLE-->
  <!--注入 google_analytics 中的 JS 代码-->
  <script src="./google_analytics.js?_inline"></script>
  <!--异步加载 Disqus 评论-->
  <script src="https://dive-into-webpack.disqus.com/embed.js" async></script>
</head>
<body>
<div id="app"></div>
<!--在这注入该页面所依赖但没有手动导入的 JavaScript-->
<!--SCRIPT-->
<!--Disqus 评论容器-->
<div id="disqus_thread"></div>
</body>
</html>

Since this template file is used as a template for all single-page applications in the project, it is no longer possible to directly write the name of Chunk in the previous section to introduce resources, because the name of Chunk to be injected into the current page is uncertain, and each single-page application will have its own name.<! --STYLE-->And<! --SCRIPT-->The purpose is to ensure that the resources that the page depends on will be injected into the generated HTML template.

web-webpack-pluginCan analyze which resources each page depends on, for example, forlogin.htmlFor example, the plug-in can determine that the page depends on the following resources:

  • Common CSS code on which all pages dependcommon.css;
  • Public JavaScrip Code on which All Pages Dependcommon.js;
  • Only CSS code that this page depends onlogin.css;
  • Only JavaScrip code that this page depends onlogin.css.

Due to the template filetemplate.htmlThere is no indication of introducing these resource-dependent HTML statements. The plug-in will automatically inject resources that are not manually imported but are page-dependent into the system according to different types.<! --STYLE-->And<! --SCRIPT-->Where they are.

  • CSS type files are injected into<! --STYLE-->Where, if<! --STYLE-->If it does not exist, it is injected at the end of the HTML HEAD tag.
  • JavaScrip type files are injected into<! --SCRIPT-->Where, if<! --SCRIPT-->If it does not exist, it is injected at the end of the HTML BODY tag.

If there are new pages to be developed in the future, you only need topagesCreate a new directory under the directory. The directory name is taken as the name of the output HTML file. The directory will only need to put down the code related to this page without changing the construction code.

Due toAutoWebPluginIt was indirectly mentioned in the previous section.WebPluginIn fact,WebPluginSupported featuresAutoWebPluginEveryone supports it.

Constructing Isomorphic Applications

Homogeneous applications refer to applications that write a code but can run in both browsers and servers.

Understanding Isomorphic Applications

At present, most views of single-page applications are rendered on the browser side through JavaScript code, but the disadvantages of rendering on the browser side include:

  • Search engines cannot include your web pages, because the displayed data are rendered asynchronously at the browser end, and most crawlers cannot obtain the data.
  • For complex single-page applications, the rendering process is computationally intensive, which may cause performance problems for low-end mobile devices. Users can obviously perceive the rendering delay of the first screen.

In order to solve the above problems, some people have raised the question of whether JavaScript rendering code originally running only in the browser can also be run on the server side, and returned after rendering HTML with content on the server side. In this way, the search engine crawler can directly capture HTML with data, and at the same time, the rendering time of the first screen can be reduced. Due to the popularity and maturity of Node.js and the introduction and implementation of virtual DOM, this assumption becomes possible.

In fact, the mainstream front-end frameworks now support isomorphism, including React, Vue2 and Angular2, among which React is the first and most mature isomorphism scheme. Since React has more users and is very similar to each other, this section only introduces how to build React homogeneous applications with Webpack.

The core of the operating principle of homogeneous application lies in the virtual DOM, which means that the original DOM structure is described by JavaScript Object instead of directly operating DOM. When DOM needs to be updated, DOM tree is not directly operated, but mapped into DOM operation after JavaScript Object is updated.

The advantages of virtual DOM are:

  • Because operating DOM tree is a time-consuming operation, minimizing DOM tree operations can optimize web page performance. The DOM Diff algorithm can find out the minimum difference between two different Object and obtain the minimum DOM operation.
  • When rendering the virtual DOM, the result can be expressed not only by operating the DOM tree, but also in other ways, such as rendering the virtual DOM into a string (server-side rendering), or rendering it into a native UI component (React Native) of the mobile phone App.

Take React as an example, the core module react is responsible for managing the life cycle of React components, and the specific rendering work can be handed over toreact-domModule to be responsible for.

react-domThere are 2 ways to render the virtual DOM tree:

  • viarender()Function to manipulate the browser DOM tree to display the results.
  • viarenderToString()Calculates a string representing the HTML form of the virtual DOM.

The ultimate goal of building homogeneous applications is to build two JavaScript codes from one project source code, one for running on the browser side and one for running and rendering HTML in the Node.js environment. Among them, JavaScript code used to run in the Node.js environment needs to pay attention to the following points:

  • Can’t include API provided by browser environment, such as usingdocumentDOM operations are performed because Node.js does not support these API;;
  • CSS code cannot be included, because the purpose of server-side rendering is to render HTML content. Rendering CSS code will add extra computation and affect server-side rendering performance.
  • You can’t put thenode_modulesIn the third party module and Node.js native module (for examplefsModules) are packaged in, but need to be introduced through the CommonJS specification.
  • You need to export a rendering function through the CommonJS specification for executing this rendering function in the HTTP server and rendering HTML content returns.

Solution

For building browser environment codewebpack.config.jsThe configuration file will remain unchanged and a new configuration file will be created specifically for building the rendering code of the server side.webpack_server.config.js, which reads as follows:

const path = require('path');
const nodeExternals = require('webpack-node-externals');

module.exports = {
  // JS 执行入口文件
  entry: './main_server.js',
  // 为了不把 Node.js 内置的模块打包进输出文件中,例如 fs net 模块等
  target: 'node',
  // 为了不把 node_modules 目录下的第三方模块打包进输出文件中
  externals: [nodeExternals()],
  output: {
    // 为了以 CommonJS2 规范导出渲染函数,以给采用 Node.js 编写的 HTTP 服务调用
    libraryTarget: 'commonjs2',
    // 把最终可在 Node.js 中运行的代码输出到一个 bundle_server.js 文件
    filename: 'bundle_server.js',
    // 输出文件都放到 dist 目录下
    path: path.resolve(__dirname, './dist'),
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        use: ['babel-loader'],
        exclude: path.resolve(__dirname, 'node_modules'),
      },
      {
        // CSS 代码不能被打包进用于服务端的代码中去,忽略掉 CSS 文件
        test: /\.css/,
        use: ['ignore-loader'],
      },
    ]
  },
  devtool: 'source-map' // 输出 source-map 方便直接调试 ES6 源码
};

The above code has several key points, namely:

  • target: 'node'Because the operating environment of the output code is Node.js, the native modules of Node.js that depend on the source code do not need to be packaged.
  • externals: [nodeExternals()] webpack-node-externalsThe aim is to preventnode_modulesThird-party modules in the directory are packed in because Node.js will go by default.node_modulesFind and use third-party modules under the directory;
  • {test: /\.css/, use: ['ignore-loader']}Ignore the dependent CSS file, CSS will affect the rendering performance of the server, and it is also an unimportant part of the server’s rendering.
  • libraryTarget:'commonjs2'The rendering function is exported in the CommonJS2 specification to supply HTTP server code calls written in Node.js.

In order to reuse code to the maximum extent, the following directory structure needs to be adjusted:

Put the root component of the page into a separate file.AppComponent.js, the file can only contain the code of the root component, not the code of the rendering portal, and the root component needs to be exported for rendering portal calls.AppComponent.jsThe content is as follows:

import React, { Component } from 'react';
import './main.css';

export class AppComponent extends Component {
  render() {
    return <h1>Hello,Webpack</h1>
  }
}

Write two different files for rendering portals in different environments, which are respectively used for rendering DOM on the browser side.main_browser.jsFile, and for the server to render HTML stringsmain_server.jsDocuments.

main_browser.jsThe document reads as follows:

import React from 'react';
import { render } from 'react-dom';
import { AppComponent } from './AppComponent';

// 把根组件渲染到 DOM 树上
render(<AppComponent/>, window.document.getElementById('app'));

main_server.jsThe document reads as follows:

In order to return the rendered complete HTML file to the requestor through HTTP service, it is also necessary to write an HTTP server using Node.js. Since this section does not focus on the implementation of HTTP server, ExpressJS is adopted to implement it.http_server.jsThe document reads as follows:

const express = require('express');
const { render } = require('./dist/bundle_server');
const app = express();

// 调用构建出的 bundle_server.js 中暴露出的渲染函数,再拼接下 HTML 模版,形成完整的 HTML 文件
app.get('/', function (req, res) {
  res.send(`
<html>
<head>
  <meta charset="UTF-8">
</head>
<body>
<div id="app">${render()}</div>
<!--导入 Webpack 输出的用于浏览器端渲染的 JS 文件-->
<script src="./dist/bundle_browser.js"></script>
</body>
</html>
  `);
});

// 其它请求路径返回对应的本地文件
app.use(express.static('.'));

app.listen(3000, function () {
  console.log('app listening on port 3000!')
});

Reinstall the newly introduced third-party dependencies:

# 安装 Webpack 构建依赖
npm i -D css-loader style-loader ignore-loader webpack-node-externals
# 安装 HTTP 服务器依赖
npm i -S express

All the above preparations have been completed, then the construction is executed and the target file is compiled:

  • executive commandwebpack --config webpack_server.config.jsBuild a system for server-side rendering./dist/bundle_server.jsDocuments.
  • executive commandwebpackTo build a browser environment for operation./dist/bundle_browser.jsFile, the default configuration file iswebpack.config.js.

After completion of construction and execution, executenode ./http_server.jsAfter starting the HTTP server, use the browser to access it.http://localhost: 3000 will see Hello,Webpack. However, in order to verify the rendering results of the server, you need to open the web capture column in the browser’s development tool and refresh the browser again to capture the package requesting HTML. the capture effect diagram is as follows:

It can be seen that the server returns the HTML after rendering the content instead of the HTML template, which shows that the transformation of homogeneous application is completed.

This example providesProject complete code

Building Electron applications

Electron is the combination of Node.js and Chromium browser. It uses the Web page displayed by Chromium browser as the GUI of the application and interacts with the operating system through Node.js. When you operate a window in an Electron application, you are actually operating a web page. When your operation needs to be completed through the operating system, the webpage will interact with the operating system through Node.js.

The advantages of developing desktop applications in this way include:

  • To lower the development threshold, one only needs to master Web page development technology and Node.js. a large number of web development technologies and off-the-shelf libraries can be reused for Electron; .
  • Because Chromium browser and Node.js are cross-platform, Electron can write a code to run on different operating systems.

When running the Electron application, it starts with starting a main process. The main process is started by Node.js executing an entry JavaScript file. this entry filemain.jsThe content is as follows:

<p data-height=”565″ data-theme-id=”0″ data-slug-hash=”vjweQv” data-default-tab=”js” data-user=”whjin” data-embed-version=”2″ data-pen-title=”Electron-main.js” class=”codepen”>See the PenElectron-main.jsby whjin (@whjin) onCodePen.</p>
<script async src=”https://static.codepen.io/ass …; ></script>

After the main process is started, it will always stay in the background and run. The window you see and operate is not the main process, but the window subprocess newly started by the main process.

The application has a series of life cycle events from start-up to exit throughelectron.app.on()Function to monitor life cycle events and respond at specific moments. For example, inapp.on('ready')Passed in the eventBrowserWindowTo show the application’s main window.

The startup window is actually a web page that will be loaded at startup.loadURLThe address of the incoming web page. Each window is a separate web page process, and the communication between windows needs to pass messages through the main process.

Generally speaking, the development of electronic applications is very similar to the development of Web applications, except that the operating environment of electronic has built-in browser and API of Node.js. besides API provided by browser, API provided by Node.js can also be used when developing web pages.

Access Webpack

Next, a simple Electron application is made, which requires a main window to be displayed after the application is started. there is a button in the main window. after clicking this button, a new window will be displayed and the web page will be developed using React.

Since each window in the Electron application corresponds to a web page, two web pages need to be developed, one for the main window.index.htmlAnd the newly opened windowlogin.html.

The following changes are required:

  • Create a new entry file for the main process under the project root directorymain.jsThe contents are consistent with those mentioned above.
  • The code for the main window page is as follows:
import React, { Component } from 'react';
import { render } from 'react-dom';
import { remote } from 'electron';
import path from 'path';
import './index.css';

class App extends Component {

  // 在按钮被点击时
  handleBtnClick() {
    // 新窗口对应的页面的 URI 地址
    const modalPath = path.join('file://', remote.app.getAppPath(), 'dist/login.html');
    // 新窗口的大小
    let win = new remote.BrowserWindow({ width: 400, height: 320 })
    win.on('close', function () {
      // 窗口被关闭时清空资源
      win = null
    })
    // 加载网页
    win.loadURL(modalPath)
    // 显示窗口
    win.show()
  }
  
  render() {
    return (
      <div>
        <h1>Page Index</h1>
        <button onClick={this.handleBtnClick}>Open Page Login</button>
      </div>
    )
  }
}

render(<App/>, window.document.getElementById('app'));

The most critical part is to pass through the button click event.electronThe API provided in the library opens a new window and loads the address of the web page file.

The code of the page part has been modified, and then the code of the construction aspect will be modified. The following points are required for construction here:

  • Build two web pages that can run in the browser, corresponding to the interfaces of the two windows respectively;
  • Because there may be calling Node.js native modules or electron modules in the JavaScript code of the webpage, that is, the output code depends on these modules. However, because these modules are all supported internally, the built-in code cannot package these modules.

It is very simple to fulfill the above requirements because the Webpack has built-in support for Electron. Just add a line of code to the Webpack configuration file, as follows:

target: 'electron-renderer',

After the above modifications are completed, re-execute the Webpack construction, and the codes required for the corresponding web pages are output to the root directory of the project.distIn the catalog.

In order to run as an Electron application, new dependencies need to be installed:


# 安装 Electron 执行环境到项目中
npm i -D electron

Building Npm module

The modules released to Npm warehouse have the following characteristics:

  • There must be a description of the module under the root directory of each module.package.jsonDocuments. This file describes which module’s entry file is, which modules the module depends on, etc.
  • The files in the module are mainly JavaScript files, but not limited to JavaScript files. For example, a UI component module may require JavaScript, CSS, picture files, etc.
  • Most of the codes in the module adopt modular specifications, because your module may depend on other modules, and other modules may depend on your module. Because the CommonJS modular specification is widely supported at present, the code uploaded to Npm warehouse is best to comply with the specification.

Raise a question

Webpack can be used not only to build running applications, but also to build modules uploaded to Npm. Next, I will teach you how to build a React component of an uploadable Npm warehouse with Webpack. The specific requirements are as follows:

  1. The source code is written in ES6, but the requirement for release to Npm warehouse is ES5 and complies with CommonJS modular specification. If the ES5 code released to Npm is converted, please also provide Source Map for debugging.
  2. Other resource files such as CSS files that the UI component depends on also need to be included in the published module.
  3. Minimize redundant code and reduce the code file size of released components.
  4. The code of the released component cannot contain the code of the module it depends on, but allows the user to selectively install it. For example, the code of React library cannot be embedded. The purpose of this is to prevent the code of React library from being repeatedly packaged when other components also depend on React library.

Before starting, look at the directory structure of the modules finally released to the Npm warehouse:

node_modules/hello-webpack
├── lib
│   ├── index.css (组件所有依赖的 CSS 都在这个文件中)
│   ├── index.css.map
│   ├── index.js (符合 CommonJS 模块化规范的 ES5 代码)
│   └── index.js.map
├── src (ES6 源码)
│   ├── index.css
│   └── index.js
└── package.json (模块描述文件)

src/index.jsThe document reads as follows:

import React, { Component } from 'react';
import './index.css';

// 导出该组件供给其它模块使用
export default class HelloWebpack extends Component {
  render() {
    return <h1 className="hello-component">Hello,Webpack</h1>
  }
}

This is the only way to use this module:

// 通过 ES6 语法导入
import HelloWebpack from 'hello-webpack';
import 'hello-webpack/lib/index.css';

// 或者通过 ES5 语法导入
var HelloWebpack = require('hello-webpack');
require('hello-webpack/lib/index.css');

// 使用 react-dom 渲染
render(<HelloWebpack/>);

Building Npm modules using Webpack

For requirement 1, this can be done:

  • Usebabel-loaderConvert ES6 code into ES5 code.
  • By openingdevtool: 'source-map'Output Source Map to publish debugging.
  • Set upoutput.libraryTarget='commonjs2'Make the output code conform to the CommonJS2 modular specification for other modules to import and use.

The relevant Webpack configuration code is as follows:

module.exports = {
  output: {
    // 输出的代码符合 CommonJS 模块化规范,以供给其它模块导入使用。
    libraryTarget: 'commonjs2',
  },
  // 输出 Source Map
  devtool: 'source-map',
};

For requirement 2, it is required to passcss-loaderAndextract-text-webpack-pluginImplementation, the relevant Webpack configuration code is as follows:

const ExtractTextPlugin = require('extract-text-webpack-plugin');

module.exports = {
  module: {
    rules: [
      {
        // 增加对 CSS 文件的支持
        test: /\.css/,
        // 提取出 Chunk 中的 CSS 代码到单独的文件中
        use: ExtractTextPlugin.extract({
          use: ['css-loader']
        }),
      },
    ]
  },
  plugins: [
    new ExtractTextPlugin({
      // 输出的 CSS 文件名称
      filename: 'index.css',
    }),
  ],
};

This step introduces 3 new dependencies:

# 安装 Webpack 构建所需要的新依赖
npm i -D style-loader css-loader extract-text-webpack-plugin

For requirement 3, it should be noted that Babel will inject some auxiliary functions when converting ES6 code into ES5 code.

For example, the following ES6 code:

class HelloWebpack extends Component{
}

The following 2 auxiliary functions are required when converting into ES5 code that can run normally:

  • babel-runtime/helpers/createClassFor implementationclassGrammar
  • babel-runtime/helpers/inheritsFor implementationextendsGrammar

By default Babel will embed the codes of these dependent auxiliary functions in each output file. If multiple source code files depend on these auxiliary functions, the codes of these auxiliary functions will appear many times repeatedly, resulting in code redundancy.

In order to prevent the generation of these auxiliary functions from repeating, they can be passed when they are relied on.require('babel-runtime/helpers/createClass')In order to make them appear only once.babel-plugin-transform-runtimePlug-ins are used to do this.

Modify.babelrcFile, join for ittransform-runtimePlug-ins:

{
  "plugins": [
    [
      "transform-runtime",
      {
        // transform-runtime 默认会自动的为你使用的 ES6 API 注入 polyfill
        // 假如你在源码中使用了 Promise,输出的代码将会自动注入 require('babel-runtime/core-js/Promise') 语句
        // polyfill 的注入应该交给模块使用者,因为使用者可能在其它地方已经注入了其它的 Promise polyfill 库
        // 所以关闭该功能
        "polyfill": false
      }
    ]
  ]
}

Due to accessionbabel-plugin-transform-runtimeThere will be a large number of similar in the generated coderequire('babel-runtime/helpers/createClass')Such statements, so the output code will depend onbabel-runtimeModules.

This step introduces 3 new dependencies:

# 安装 Webpack 构建所需要的新依赖
npm i -D babel-plugin-transform-runtime
# 安装输出代码运行时所需的新依赖
npm i -S babel-runtime

For requirement 4, it is required to pass the test inOther configuration itemsAs described inExternalsTo achieve.

External is used to tell which modules are not to be packaged in the code to be built by the Webpack, that is to say, these templates are provided by the external environment, and the Webpack can ignore them when packaging.

The relevant Webpack configuration code is as follows:


module.exports = {
  // 通过正则命中所有以 react 或者 babel-runtime 开头的模块
  // 这些模块通过注册在运行环境中的全局变量访问,不用被重复打包进输出的代码里
  externals: /^(react|babel-runtime)/,
};

After the above configuration is turned on, there will be import in the output codereactOr ..babel-runtimeModule code, but theirreactOr ..babel-runtimeThe contents of will not be included, as follows:

[
    (function (module, exports) {
        module.exports = require("babel-runtime/helpers/inherits");
    }),
    (function (module, exports) {
        module.exports = require("react");
    })
]

In this way, the output file does not store the code of react or babel-runtime module while maintaining the correctness of the code.

In fact, when you develop Npm modules, you do not only need to deal with react and babel-runtime modules, but also need to deal with all the modules on which the modules being developed depend. Because the module on which the module being developed depends may also be dependent on other modules. When a module in a project is dependent multiple times, the Webpack will only pack it once.

The complete configuration code of the final Webpack after completing the above 4 steps is as follows:

const path = require('path');
const ExtractTextPlugin = require('extract-text-webpack-plugin');

module.exports = {
  // 模块的入口文件
  entry: './src/index.js',
  output: {
    // 输出文件的名称
    filename: 'index.js',
    // 输出文件的存放目录
    path: path.resolve(__dirname, 'lib'),
    // 输出的代码符合 CommonJS 模块化规范,以供给其它模块导入使用。
    libraryTarget: 'commonjs2',
  },
  // 通过正则命中所有以 react 或者 babel-runtime 开头的模块,
  // 这些模块使用外部的,不能被打包进输出的代码里,防止它们出现多次。
  externals: /^(react|babel-runtime)/,
  module: {
    rules: [
      {
        test: /\.js$/,
        use: ['babel-loader'],
        // 排除 node_modules 目录下的文件,
        // node_modules 目录下的文件都是采用的 ES5 语法,没必要再通过 Babel 去转换。
        exclude: path.resolve(__dirname, 'node_modules'),
      },
      {
        // 增加对 CSS 文件的支持
        test: /\.css/,
        // 提取出 Chunk 中的 CSS 代码到单独的文件中
        use: ExtractTextPlugin.extract({
          use: ['css-loader']
        }),
      },
    ]
  },
  plugins: [
    new ExtractTextPlugin({
      // 输出的 CSS 文件名称
      filename: 'index.css',
    }),
  ],
  // 输出 Source Map
  devtool: 'source-map',
};

After re-executing the build, you will see a new directory under the project directory.lib, which contains the final code to be released to the Npm repository.

Post to Npm

Before releasing the built code to the Npm repository, you also need to ensure your module description filepackage.jsonIs correctly configured.

Since the entry file of the built code is./lib/index.js, need to be modifiedpackage.jsonhit the targetmainThe fields are as follows:

{
  "main": "lib/index.js",
  "jsnext:main": "src/index.js"
}

among themjsnext:mainThe field is used to indicate the location of the module entry file written using ES6.

After modification, it will be executed under the project directory.npm publishYou can publish the built code to the Npm repository (make sure you havenpm loginOver).

If you want to keep the code released to Npm consistent with the directory structure of the source code, then using Webpack is not suitable. Because the source code is divided into modular files, and the Webpack will combine these modules together. Although the files output by the Webpack can also adopt the CommonJS modular syntax, it is not suitable to package all modules into one file and publish to Npm in some scenarios. For example, a tool function library such as Lodash may only use a few of the tool functions in the project. if all the tool functions are packaged in one file, then all the tool functions will be packaged in, while keeping the independence of the module file can only be packaged in the used one. There is also a library made up of a large number of independent components like UI component library, which is similar to Lodash.
Therefore, Webpack is suitable for building complete and indivisible Npm modules.

Building Offline Applications

The core of off-line application is off-line caching technology. There have been two kinds of off-line caching technologies in history. They are:

  1. AppCache is also called Application Cache and has been removed from the Web standard. please try not to use it.
  2. Service WorkersIt is the latest offline caching technology and part of the Web Worker. It implements offline caching by intercepting network requests, which is more flexible than AppCache. It is also builtPWAOne of the key technologies applied.

Know Service Workers

Service Workers is a script that runs in the background of a browser, and its life cycle is completely independent of web pages. It cannot directly access DOM, but it can send messages through the postMessage interface to communicate with UI processes. Intercepting network requests is an important function of Service Workers, through which offline caching, editing responses, filtering responses and other functions can be completed.

Service Workers compatibility

At present, Chrome, Firefox and Opera have all fully supported Service Workers, but they are not optimistic about mobile browsers, only the higher version of Android supports them. Because Service Workers cannot pass injectionpolyfillTo achieve compatibility, so before you plan to use it, please investigate the running scene of your web page.

The simplest way to determine whether the browser supports Service Workers is through the following code:

// 如果 navigator 对象上存在 serviceWorker 对象,就表示支持
if (navigator.serviceWorker) {
  // 通过 navigator.serviceWorker 使用
}

Register Service Workers

To connect a web page with Service Workers, a script describing the logic of Service Workers needs to be registered after the web page is loaded. The code is as follows:

if (navigator.serviceWorker) {
  window.addEventListener('DOMContentLoaded',function() {
    // 调用 serviceWorker.register 注册,参数 /sw.js 为脚本文件所在的 URL 路径
      navigator.serviceWorker.register('/sw.js');
  });
}

Once this script file is loaded, the installation of Service Workers will begin. After the script is installed in the browser, it will still exist even if the user closes the current web page. In other words, the logic of Service Workers will not take effect when the web page is first opened, because the script has not been loaded and registered, but the logic in the script will take effect when the web page is opened again later.

In Chrome, you can open the websitechrome://inspect/#service-workersTo view all registered Service Workers in the current browser.

Use Service Workers to implement offline caching.

After successful registration, Service Workers will send out some events in its life cycle and do some things on the characteristic time node by monitoring the corresponding events.

In the Service Workers script, a new keyword was introducedselfRepresents the current Service Workers instance.

Dispatched after successful installation of Service WorkersinstallEvent, the logic of caching resources needs to be executed in this event. The implementation code is as follows:

// 当前缓存版本的唯一标识符,用当前时间代替
var cacheKey = new Date().toISOString();

// 需要被缓存的文件的 URL 列表
var cacheFileList = [
  '/index.html',
  '/app.js',
  '/app.css'
];

// 监听 install 事件
self.addEventListener('install', function (event) {
  // 等待所有资源缓存完成时,才可以进行下一步
  event.waitUntil(
    caches.open(cacheKey).then(function (cache) {
      // 要缓存的文件 URL 列表
      return cache.addAll(cacheFileList);
    })
  );
});

Next, you need to monitor the network request events to intercept the request and reuse the cache. The code is as follows:

self.addEventListener('fetch', function(event) {
  event.respondWith(
    // 去缓存中查询对应的请求
    caches.match(event.request).then(function(response) {
        // 如果命中本地缓存,就直接返回本地的资源
        if (response) {
          return response;
        }
        // 否则就去用 fetch 下载资源
        return fetch(event.request);
      }
    )
  );
});

The above realizes offline caching.

Update cache

Online codes sometimes need to be updated and republished. If this file is cached offline, it needs corresponding logic in the Service Workers script to update the cache. This can be done by updating the Service Workers script file.

The browser has the following mechanisms for Service Workers:

  1. Every time you open a web page that has access to Service Workers, the browser will download the Service Workers script file again (so please note that the script file cannot be too large). If there is a byte difference between the script file and the currently registered file, it will be regarded as a “new service worker thread”.
  2. The new Service Workers thread will start and will trigger itinstallEvents.
  3. When the currently open page on the website closes, the old Service Workers thread will be terminated and the new Service Workers thread will take control.
  4. After the new Service Workers thread takes control, it will trigger its activate event.

The activate event in the new Service Workers thread is the best point in time to clean up the old cache. The code is as follows:

// 当前缓存白名单,在新脚本的 install 事件里将使用白名单里的 key 
var cacheWhitelist = [cacheKey];

self.addEventListener('activate', function(event) {
  event.waitUntil(
    caches.keys().then(function(cacheNames) {
      return Promise.all(
        cacheNames.map(function(cacheName) {
          // 不在白名单的缓存全部清理掉
          if (cacheWhitelist.indexOf(cacheName) === -1) {
            // 删除缓存
            return caches.delete(cacheName);
          }
        })
      );
    })
  );
});

The final complete code Service Workers script code is as follows:

// 当前缓存版本的唯一标识符,用当前时间代替
var cacheKey = new Date().toISOString();

// 当前缓存白名单,在新脚本的 install 事件里将使用白名单里的 key
var cacheWhitelist = [cacheKey];

// 需要被缓存的文件的 URL 列表
var cacheFileList = [
  '/index.html',
  'app.js',
  'app.css'
];

// 监听 install 事件
self.addEventListener('install', function (event) {
  // 等待所有资源缓存完成时,才可以进行下一步
  event.waitUntil(
    caches.open(cacheKey).then(function (cache) {
      // 要缓存的文件 URL 列表
      return cache.addAll(cacheFileList);
    })
  );
});

// 拦截网络请求
self.addEventListener('fetch', function (event) {
  event.respondWith(
    // 去缓存中查询对应的请求
    caches.match(event.request).then(function (response) {
        // 如果命中本地缓存,就直接返回本地的资源
        if (response) {
          return response;
        }
        // 否则就去用 fetch 下载资源
        return fetch(event.request);
      }
    )
  );
});

// 新 Service Workers 线程取得控制权后,将会触发其 activate 事件
self.addEventListener('activate', function (event) {
  event.waitUntil(
    caches.keys().then(function (cacheNames) {
      return Promise.all(
        cacheNames.map(function (cacheName) {
          // 不在白名单的缓存全部清理掉
          if (cacheWhitelist.indexOf(cacheName) === -1) {
            // 删除缓存
            return caches.delete(cacheName);
          }
        })
      );
    })
  );
});

Access Webpack

The key problem to be solved in building off-line application accessing Service Workers with Webpack is how to generate the above mentionedsw.jsDocuments, andsw.jsIn the filecacheFileListVariable, which represents the URL list of files to be cached, needs to be determined according to the URL corresponding to the output file list, instead of being written as a static value as above.

If the file directory structure of the build output is:

├── app_4c3e186f.js
├── app_7cc98ad0.css
└── index.html

Thensw.jsIn the filecacheFileListThe value of should be:

var cacheFileList = [
  '/index.html',
  'app_4c3e186f.js',
  'app_7cc98ad0.css'
];

The Webpack does not have native features to fulfill the above requirements. Fortunately, a plug-in has been prepared for us in a large community.serviceworker-webpack-pluginThe above problems can be conveniently solved. The configuration of the Webpack after using this plug-in is as follows:

const path = require('path');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const { WebPlugin } = require('web-webpack-plugin');
const ServiceWorkerWebpackPlugin = require('serviceworker-webpack-plugin');

module.exports = {
  entry: {
    app: './main.js'// Chunk app 的 JS 执行入口文件
  },
  output: {
    filename: '[name].js',
    publicPath: '',
  },
  module: {
    rules: [
      {
        test: /\.css/,// 增加对 CSS 文件的支持
        // 提取出 Chunk 中的 CSS 代码到单独的文件中
        use: ExtractTextPlugin.extract({
          use: ['css-loader'] // 压缩 CSS 代码
        }),
      },
    ]
  },
  plugins: [
    // 一个 WebPlugin 对应一个 HTML 文件
    new WebPlugin({
      template: './template.html', // HTML 模版文件所在的文件路径
      filename: 'index.html' // 输出的 HTML 的文件名称
    }),
    new ExtractTextPlugin({
      filename: `[name].css`,// 给输出的 CSS 文件名称加上 Hash 值
    }),
    new ServiceWorkerWebpackPlugin({
      // 自定义的 sw.js 文件所在路径
      // ServiceWorkerWebpackPlugin 会把文件列表注入到生成的 sw.js 中
      entry: path.join(__dirname, 'sw.js'),
    }),
  ],
  devServer: {
    // Service Workers 依赖 HTTPS,使用 DevServer 提供的 HTTPS 功能。
    https: true,
  }
};

There are two points to note for the above configuration:

  • Since Service Workers must intercept network requests in HTTPS environment to implement offline caching, HTTPS service is implemented in the way mentioned in DevServer https.
  • serviceworker-webpack-pluginPlug-ins allow users to customize in order to ensure flexibility.sw.jsTo build the output of thesw.jsA variable is injected into the header of the file.serviceWorkerOption.assetsAt the global level, there is a URL list of all files that need to be cached.

Need to modify the abovesw.jsA that has static values written in the filecacheFileListAs follows:

// 需要被缓存的文件的 URL 列表
var cacheFileList = global.serviceWorkerOption.assets;

All the above files have been modified. Before rebuilding, install the newly introduced dependencies:

npm i -D serviceworker-webpack-plugin webpack-dev-server

After successful installation, execute under the root directory of the projectwebpack-dev-serverAfter the command, DevServer starts in HTTPS mode.

With Npm Script

Npm Script is a task executor. Npm is a package manager that comes with the installation of Node.js Npm Script is a built-in feature of Npm that allows you topackage.jsonFor use in documentsscriptsField definition task:

{
  "scripts": {
    "dev": "node dev.js",
    "pub": "node build.js"
  }
}

insidescriptsA field is an object, and each attribute corresponds to a script. Two tasks are defined above.devAndpub. The underlying implementation principle of Npm Script is to call the Shell to run script commands, such as executingnpm run pubCommands are equivalent to executing commands.node build.js.

Npm Script also has an important function that can be run and installed in the project directory.node_modulesIn the executable module, such as through the command:

npm i -D webpack

After the Webpack is installed in the project, it is not possible to directly execute the webpack construction under the root directory of the project through the command Webpack, but through the command./node_modules/.bin/webpackGo ahead.

Npm Script can easily solve this problem, only by usingscriptsField to define a task, such as:

{
  "scripts": {
    "build": "webpack"
  }
}

Npm Script will go to the project directory first.node_modulesFind out if there is any executablewebpackIf there are files, use local ones; if there are no files, use global ones. Therefore, to execute the Webpack construction now only needs to be executednpm run buildTo realize.

Why does Webpack need Npm Script

Webpack is just a tool for packaging modular code and does not provide any task management related functions. However, in the actual scenario, it is usually not possible to complete all tasks only by executing the webpack, but it requires multiple tasks to complete.

  1. In order to improve the development experience in the development phase, DevServer is used for development, and the Source Map needs to be output to facilitate debugging. at the same time, the automatic refresh function needs to be turned on.
  2. In order to reduce the size of the code posted online, the output code needs to be compressed when building the code posted online.
  3. After building the code to be published online, the built code needs to be submitted to the publishing system.

It can be seen that requirement 1 and requirement 2 conflict with each other, in which task 3 depends on task 2. To meet the above three requirements, three different tasks need to be defined.

The following three tasks are defined by Npm Script:

"scripts": {
  "dev": "webpack-dev-server --open",
  "dist": "NODE_ENV=production webpack --config webpack_dist.config.js",
  "pub": "npm run dist && rsync dist"
},

The meanings are respectively:

  • devRepresents a task that is executed during development and starts the build through DevServer. Therefore, only execution is required when developing a project.npm run dev.
  • distOn behalf of the build code for publishing online, output todistIn the catalog. thereinNODE_ENV=productionTo inject environment variables when running a task.
  • pubOn behalf of the first build code for release online, then synchronizationdistThe files in the directory go to the publishing system (how to synchronize the files depends on the publishing system you use). So after the development need to be released only need to executenpm run pub.

The advantage of using Npm Script is that it simplifies a series of complicated processes into a simple command, and only needs to execute the corresponding short command when needed, instead of manually repeating the whole process. This will greatly improve our efficiency and reduce the error rate.

Check code

The check code is very similar to Code Review, both of which are to examine the problems that may exist in the submitted code. However, Code Review is generally performed by people, while checking code is performed by machines to perform some automated checks. Automated check code costs less and implementation costs less.

The check code mainly checks the following items:

  • Code style: project members are forced to abide by a unified code style, such as how to tighten up and how to write comments, so as to ensure the readability of the code and not waste time arguing about how to write the code better.
  • Potential problems: Analyze potential bugs that may occur during the code’s operation.

At present, there are mature tools that can test common languages such as JavaScript, TypeScript, CSS, SCSS, etc.

Check JavaScript

At present, the most commonly used JavaScript checking tool is ESlint, which not only has a large number of commonly used checking rules built in, but also can be flexibly extended through plug-in mechanism.

The use of ESlint is very simple, through:npm i -g eslint

According to the global, then in the project directory:eslint init

To create a new ESlint profile.eslintrc, the file format is JSON.

If you want to override the default inspection rules or add new inspection rules, you need to modify the file, for example, using the following configuration:

{
    // 从 eslint:recommended 中继承所有检查规则
    "extends": "eslint:recommended",
    // 再自定义一些规则     
    "rules": {
        // 需要在每行结尾加 ;        
        "semi": ["error", "always"],
        // 需要使用 "" 包裹字符串         
        "quotes": ["error", "double"]
    }
}

After the configuration file is written, execute:

eslint yourfile.js

Go checkyourfile.jsFile, if your file fails the check, ESlint will output the error reason, for example:

Check TypeScript

Tslnt is a TypeScript code checking tool similar to ESlint, except that tslnt only focuses on checking TypeScript codes.

Tslnt and ESlint are used in a very similar way, first of all by:npm i -g tslint

Install to the global and execute under the root directory of the project:tslint --init

Generate configuration filetslint.json, after the configuration, then perform:tslint yourfile.tsGo checkyourfile.tsDocuments.

Check CSS

Stylelint is currently the most mature CSS checking tool, with a large number of built-in checking rules and plug-in mechanisms to allow users to customize extensions. Stylelint is based on PostCSS and can check any code that PostCSS can parse, such as SCSS, Less, etc.

First passnpm i -g stylelint

After installation to the global, go to the project root directory to create a new one.stylelintrcConfiguration file in JSON format similar to ESLint configuration, for example:

{
  // 继承 stylelint-config-standard 中的所有检查规则
  "extends": "stylelint-config-standard",
  // 再自定义检查规则  
  "rules": {
    "at-rule-empty-line-before": null
  }
}

After configuration, execute again.stylelint "yourfile.css"Go checkyourfile.cssDocuments.

Check code with Webpack

Combined with ESLint

eslint-loaderESLint can be easily integrated into Webpack by using the following methods:

module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        // node_modules 目录的下的代码不用检查
        exclude: /node_modules/,
        loader: 'eslint-loader',
        // 把 eslint-loader 的执行顺序放到最前面,防止其它 Loader 把处理后的代码交给 eslint-loader 去检查
        enforce: 'pre',
      },
    ],
  },
}

After accessing eslint-loader, you can see the error log of ESLint output in the console.

Combined with TSLint

tslint-loaderIs a peaceeslint-loaderSimilar Webpack Loader can easily integrate TSLint into Webpack, and its usage method is as follows:

module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        // node_modules 目录的下的代码不用检查
        exclude: /node_modules/,
        loader: 'tslint-loader',
        // 把 tslint-loader 的执行顺序放到最前面,防止其它 Loader 把处理后的代码交给 tslint-loader 去检查
        enforce: 'pre',
      },
    ],
  },
}

Combine stylelint

StyleLintPlugin can integrate stylelint into Webpack, and its usage is very simple, as follows:


const StyleLintPlugin = require('stylelint-webpack-plugin');

module.exports = {
  // ...
  plugins: [
    new StyleLintPlugin(),
  ],
}

some advices

Integrating code checking into a Webpack can cause the following problems:

  • Due to the large amount of computation in performing the inspection steps, integration into the Webpack will lead to slow construction.
  • After the integration code checks the Webpack, the output error information locates the error through the line number, and there is no editor to integrate and display the error intuitively.

In order to avoid the above problems, you can also do this:

  • Use the editor with integrated code checking function to make the editor display errors visually in real time.
  • Put the code checking step into the code submission, that is to say, call the above checking tools to check the code before the code submission, and submit the code only when the checks pass, so as to ensure that the code submitted to the warehouse has passed the checks.

If your project is managed using Git, Git provides Hook functionality to trigger the execution of scripts before submitting code.

Husky can easily and quickly access Git Hook for the project and execute it.npm i -D husky

When installing husky, husky will passNpm Script HookAutomatically configure Git Hook, all you need to do ispackage.jsonSeveral scripts are defined in the file as follows:

{
  "scripts": {
    // 在执行 git commit 前会执行的脚本  
    "precommit": "npm run lint",
    // 在执行 git push 前会执行的脚本  
    "prepush": "lint",
    // 调用 eslint、stylelint 等工具检查代码
    "lint": "eslint && stylelint"
  }
}

precommitAndprepushYou need to choose one according to your own situation, not both.

Jsapi to start Webpack

In addition to providing executable command line tools, Webpack also provides libraries that can be called in the Node.js environment. Through the exposed API of the Webpack, the Webpack can be directly called in the Node.js program to execute the construction.

It is more flexible to call and execute the Webpack through API than to start directly through executable files. It can be used in some special scenarios. The following will teach you how to use the API provided by the Webpack.

Webpack is actually a Node.js application, which is completely developed through JavaScript. Execute on command linewebpackCommands are actually equivalent to execution.node ./node_modules/webpack/bin/webpack.js.

Installing and using Webpack modules

Before calling the Webpack API, you need to install it:

npm i -D webpack

After successful installation, you can import the Webpack module with the following code:

const webpack = require('webpack');

// ES6 语法
import webpack from "webpack";

ExportedwebpackIs actually a function, using the following method:

webpack({
  // Webpack 配置,和 webpack.config.js 文件一致
}, (err, stats) => {
  if (err || stats.hasErrors()) {
    // 构建过程出错
  }
  // 成功执行完构建
});

If your Webpack configuration is written inwebpack.config.jsFile, can be used like this:

// 读取 webpack.config.js 文件中的配置
const config = require('./webpack.config.js');
webpack(config , callback);

Run in listening mode

The above method using the Webpack API can only be built once, and the Webpack cannot be started in listening mode. In order to start in listening mode when using the API, you need to obtain a Compiler instance, as follows:

// 如果不传 callback 回调函数,就会返回一个 Compiler 实例,用于让你去控制启动,而不是像上面那样立即启动
const compiler = webpack(config);

// 调用 compiler.watch 以监听模式启动,返回的 watching 用于关闭监听
const watching = compiler.watch({
  // watchOptions
  aggregateTimeout: 300,
},(err, stats)=>{
  // 每次因文件发生变化而重新执行完构建后
});

// 调用 watching.close 关闭监听 
watching.close(()=>{
  // 在监听关闭后
});

Using Webpack Dev Middleware

DevServer is a small HTTP server that is convenient to develop. DevServer is actually based onwebpack-dev-middlewareAnd webpack-dev-middleware is actually a middleware of Expressjs.

In other words, the code that implements the basic functions of DevServer is roughly as follows:

const express = require('express');
const webpack = require('webpack');
const webpackMiddleware = require('webpack-dev-middleware');

// 从 webpack.config.js 文件中读取 Webpack 配置 
const config = require('./webpack.config.js');
// 实例化一个 Expressjs app
const app = express();

// 用读取到的 Webpack 配置实例化一个 Compiler
const compiler = webpack(config);
// 给 app 注册 webpackMiddleware 中间件
app.use(webpackMiddleware(compiler));
// 启动 HTTP 服务器,服务器监听在 3000 端口 
app.listen(3000);

As can be seen from the above code, fromwebpack-dev-middlewareDerived fromwebpackMiddlewareIs a function that needs to receive a Compiler instance. Exported by Webpack APIwebpackThe function returns a Compiler instance.

webpackMiddlewareThe return result of the function is an Expressjs middleware, which has the following functions:

  • Receives the file output from the Webpack Compiler instance, but does not output the file to the hard disk, but saves it in memory.
  • Registering a route on Expressjs app, intercepting the request received by HTTP, and responding to the corresponding file content according to the request path;

viawebpack-dev-middlewareCan integrate DevServer into your existing HTTP server so that your existing HTTP server can return the content built by the Webpack instead of starting multiple HTTP servers during development. This is especially applicable to projects written by Node.js for backend interface services.

Configuration items supported by Webpack Dev Middleware

When calling the API provided by webpack-dev-middleware in Node.js, you can also pass in some configuration items to it as follows:

// webpackMiddleware 函数的第二个参数为配置项
app.use(webpackMiddleware(compiler, {
    // webpack-dev-middleware 所有支持的配置项
    // 只有 publicPath 属性为必填,其它都是选填项

    // Webpack 输出资源绑定在 HTTP 服务器上的根目录,
    // 和 Webpack 配置中的 publicPath 含义一致 
    publicPath: '/assets/',

    // 不输出 info 类型的日志到控制台,只输出 warn 和 error 类型的日志
    noInfo: false,

    // 不输出任何类型的日志到控制台
    quiet: false,

    // 切换到懒惰模式,这意味着不监听文件变化,只会在请求到时再去编译对应的文件,
    // 这适合页面非常多的项目。
    lazy: true,

    // watchOptions
    // 只在非懒惰模式下才有效
    watchOptions: {
        aggregateTimeout: 300,
        poll: true
    },

    // 默认的 URL 路径, 默认是 'index.html'.
    index: 'index.html',

    // 自定义 HTTP 头
    headers: {'X-Custom-Header': 'yes'},

    // 给特定文件后缀的文件添加 HTTP mimeTypes ,作为文件类型映射表
    mimeTypes: {'text/html': ['phtml']},

    // 统计信息输出样式
    stats: {
        colors: true
    },

    // 自定义输出日志的展示方法
    reporter: null,

    // 开启或关闭服务端渲染
    serverSideRender: false,
}));

Webpack Dev Middleware and module hot replacement.

DevServer provides a convenient function, which can automatically replace the old modules in the webpage when monitoring the file changes, so as to achieve real-time preview.

Although DevServer is based onwebpack-dev-middlewareYes, butwebpack-dev-middlewareThe module hot-swap function was not implemented, but DevServer implemented the function itself.

In order to be in usewebpack-dev-middlewareIt is also possible to use the module thermal replacement function to improve the development efficiency, requiring additional re-access.webpack-hot-middleware. The following modifications are required to implement module hot replacement.

Step 1: Modifywebpack.config.jsDocuments, joiningHotModuleReplacementPluginPlug-in, modify as follows:

const HotModuleReplacementPlugin = require('webpack/lib/HotModuleReplacementPlugin');

module.exports = {
  entry: [
    // 为了支持模块热替换,注入代理客户端
    'webpack-hot-middleware/client',
    // JS 执行入口文件
    './src/main.js'
  ],
  output: {
    // 把所有依赖的模块合并输出到一个 bundle.js 文件
    filename: 'bundle.js',
  },
  plugins: [
    // 为了支持模块热替换,生成 .hot-update.json 文件
    new HotModuleReplacementPlugin(),
  ],
  devtool: 'source-map',
};

Step 2: Modify HTTP Server Codeserver.jsFile, accesswebpack-hot-middlewareMiddleware, modified as follows:

const express = require('express');
const webpack = require('webpack');
const webpackMiddleware = require('webpack-dev-middleware');

// 从 webpack.config.js 文件中读取 Webpack 配置
const config = require('./webpack.config.js');
// 实例化一个 Expressjs app
const app = express();

// 用读取到的 Webpack 配置实例化一个 Compiler
const compiler = webpack(config);
// 给 app 注册 webpackMiddleware 中间件
app.use(webpackMiddleware(compiler));
// 为了支持模块热替换,响应用于替换老模块的资源
app.use(require('webpack-hot-middleware')(compiler));
// 把项目根目录作为静态资源目录,用于服务 HTML 文件
app.use(express.static('.'));
// 启动 HTTP 服务器,服务器监听在 3000 端口
app.listen(3000, () => {
  console.info('成功监听在 3000');
});

Step 3: Modify the Execution Entry Filemain.js, add replacement logic, add the following code at the end of the file:

if (module.hot) {
  module.hot.accept();
}

Step 4: Install Newly Introduced Dependencies:

npm i -D webpack-dev-middleware webpack-hot-middleware express

After the installation is successful, pass thenode ./server.jsYou can start a custom HTTP service similar to DevServer that supports hot module replacement.

This example providesProject complete code

Load picture

It is inevitable to rely on picture resources in web pages, such as PNG, JPG and GIF. Here’s how to load picture resources with Webpack.

Usefile-loader

file-loaderYou can replace the statement of importing pictures in JavaScript and CSS with the correct address, and output the file to the corresponding location at the same time.

For example, CSS source code is written like this:

#app {
  background-image: url(./imgs/a.png);
}

Befile-loaderThe CSS output after conversion will look like this:

#app {
  background-image: url(5556e1251a78c5afda9ee7dd06ad109b.png);
}

And in the output directorydistIn addition, there are more./imgs/a.pngCorresponding picture filehash.pngThe output file name is the Hash value calculated according to the file content.

Similarly, the source code for importing pictures into JavaScript is as follows:

import imgB from './imgs/b.png';

window.document.getElementById('app').innerHTML = `
<img src="${imgB}"/>
`;

afterfile-loaderJavaScript code output after processing is as follows:

module.exports = __webpack_require__.p + "0bcc1f8d385f78e1271ebfca50668429.png";

That is to sayimgBThe value of is the URL address corresponding to the picture.

Use in Webpackfile-loaderIt is very simple. The relevant configuration is as follows:

module.exports = {
  module: {
    rules: [
      {
        test: /\.png$/,
        use: ['file-loader']
      }
    ]
  }
};

Useurl-loader

url-loaderThe contents of the file can be passed throughbase64After coding, it is injected into JavaScript or CSS.

For example, CSS source code is written like this:

#app {
  background-image: url(./imgs/a.png);
}

Beurl-loaderThe CSS output after conversion will look like this:

#app {
  background-image: url(data:image/png;base64,iVBORw01afer...); /* 结尾省略了剩下的 base64 编码后的数据 */
}

Similarly, the effect is similar in JavaScript.

As can be seen from the above exampleurl-loaderIt will calculate according to the content of the picturebase64Encoded strings are directly injected into the code. Due to the huge amount of general picture data, JavaScript and CSS files will also become larger. So in useurl-loaderIt is important to note that the image size should not be too large, otherwise it will lead to the slow loading of web pages caused by too large JavaScript and CSS files.

General utilizationurl-loaderInject the small picture resources needed by the webpage into the code to reduce the loading times. Because in the HTTP/1 protocol, an HTTP link needs to be established every time a resource is loaded, it is not cost-effective to create an HTTP connection for a very small picture.

url-loaderConsidering the above problems, and provides a convenient choice.limitThis option is used to control when the file size is less thanlimitOnly use whenurl-loaderOtherwise, use thefallbackOptionloader. The relevant Webpack configurations are as follows:

module.exports = {
  module: {
    rules: [
      {
        test: /\.png$/,
        use: [{
          loader: 'url-loader',
          options: {
            // 30KB 以下的文件采用 url-loader
            limit: 1024 * 30,
            // 否则采用 file-loader,默认值就是 file-loader 
            fallback: 'file-loader',
          }
        }]
      }
    ]
  },
};

In addition, you can also do the following optimization:

The above method of loading pictures is also applicable to other binary types of resources, such as PDF, SWF, etc.

This example providesProject complete code

Load SVG

SVG, as a standard format of vector graph, has been supported by various browsers and has become a synonym for vector graph in the Web. Using SVG instead of bitmap in web pages has the following advantages:

  1. SVG is clearer than bitmap, and it will not destroy the clarity of graphics under arbitrary scaling. SVG can easily solve the problem of unclear image display under high-resolution screen.
  2. In the case of simple graphics lines, the size of SVG file is smaller than bitmap. In today’s flat UI, SVG will be smaller in most cases.
  3. SVG with the same graphics has better rendering performance than the corresponding high-definition graphics.
  4. SVG uses XML syntax description consistent with HTML, which is very flexible.

Drawing tools can export one by one.svgFile, SVG import method is similar to picture, can be used directly in CSS as follows:

body {
  background-image: url(./svgs/activity.svg);
}

Can also be used in HTML:

<img src="./svgs/activity.svg"/>

That is to say, SVG files can be used directly as a picture in exactly the same way as when using pictures.

Usefile-loaderAnd useurl-loaderIt is also valid for SVG, just change the file suffix in the Loader test configuration to.svg, code is as follows:

module.exports = {
  module: {
    rules: [
      {
        test: /\.svg/,
        use: ['file-loader']
      }
    ]
  },
};

Since SVG is a file in text format, there are other methods besides the above two methods, which will be explained one by one below.

Useraw-loader

raw-loaderThe contents of the text file can be read out and injected into JavaScript or CSS.

For example, write this in JavaScript:

import svgContent from './svgs/alert.svg';

afterraw-loaderThe output code after processing is as follows:

module.exports = "<svg xmlns=\"http://www.w3.org/2000/svg\"... </svg>" // 末尾省略 SVG 内容

That is to saysvgContentThe content of SVG is equivalent to SVG in string form. Since SVG itself is an HTML element, after obtaining SVG content, SVG can be directly inserted into web pages through the following code:


window.document.getElementById('app').innerHTML = svgContent;

Useraw-loaderWhen the relevant Webpack configuration is as follows:

module.exports = {
  module: {
    rules: [
      {
        test: /\.svg$/,
        use: ['raw-loader']
      }
    ]
  }
};

Due toraw-loaderIt will directly return the text content of SVG and cannot display the text content of SVG through CSS. Therefore, SVG cannot be imported into CSS after adopting this method. In other words, it cannot appear in CSS.background-image: url(./svgs/activity.svg)Such code, becausebackground-image: url(<svg>...</svg>)It is illegal.

Usesvg-inline-loader

svg-inline-loaderAnd the above mentionedraw-loaderVery similar, except thatsvg-inline-loaderThe content of SVG will be analyzed and some unnecessary codes will be removed to reduce the file size of SVG.

After SVG is produced using drawing tools such as Adobe Illustrator and Sketch, these tools will generate code that is not necessary for the web page to run when exporting. For example, the following is the code for SVG exported by Sketch:

<svg class="icon" verison="1.1" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"
     stroke="#000">
  <circle cx="12" cy="12" r="10"/>
</svg>

Besvg-inline-loaderAfter processing, it will be reduced to the following:

<svg viewBox="0 0 24 24" stroke="#000"><circle cx="12" cy="12" r="10"/></svg>

That is to saysvg-inline-loaderThe compression function of SVG is added.

Usesvg-inline-loaderWhen the relevant Webpack configuration is as follows:

module.exports = {
  module: {
    rules: [
      {
        test: /\.svg$/,
        use: ['svg-inline-loader']
      }
    ]
  }
};   

Load Source Map

Since new languages are often used to develop projects in the development process, the source code will be converted into JavaScript code that can run directly in the browser. Although this can improve the development efficiency, you will find that the generated code is very unreadable in the process of debugging code, which brings inconvenience to code debugging.

Webpack supports outputting the corresponding Source Map file for the code generated by the conversion, so as to facilitate debugging through the source code in the browser. The Webpack configuration item that controls the Source Map output isdevtool, it has many options, let’s introduce one by one in detail.

devtool Meaning
Empty Source Map not generated
eval Each ..moduleWill be packaged intoevalWrapped up in a bag and executed in eachevalAdd a comment at the end of the statement//# sourceURL=webpack:///./main.js
source-map A separate Source Map file is additionally generated and appended at the end of the JavaScript file//# sourceMappingURL=bundle.js.map
hidden-source-map Andsource-mapSimilar, but not appended to the end of the JavaScript file//# sourceMappingURL=bundle.js.map
inline-source-map Andsource-mapSimilarly, but instead of generating a separate Source Map file, Source Map is converted tobase64Encoding is embedded in JavaScript
eval-source-map AndevalSimilar, but will convert each module’s Source Map tobase64Encoding embedded intoevalAt the end of the statement, for example//# sourceMappingURL=data:application/json; charset=utf-8; base64,eyJ2ZXJzaW ...
cheap-source-map Andsource-mapSimilarly, but the generated Source Map file has no column information, so the generation speed is faster.
cheap-module-source-map Andcheap-source-mapSimilar, but includes Source Map generated by Loader

In fact, the above table only listsdevtoolPart of the possible value, its value can actually be determined bysource-mapevalinlinehiddencheapmoduleThese six keywords are randomly combined. Each of these six keywords represents a feature, and their meanings are:

  • eval: useevalStatement wraps the module to be installed;
  • source-map: generate a separate Source Map file;
  • hidden: Do not point out the Source Map file in the JavaScript file, so the browser will not automatically load the source map;
  • inline: converts the generated Source Map tobase64The format is embedded in JavaScript files;
  • cheap: the generated Source Map will not contain column information, thus the calculation amount is smaller and the output Source Map file is smaller; At the same time, Source Map output by Loader will not be adopted;
  • module: Source Map from Loader is simply processed into one module per line;

How to choose

If you don’t care about details and performance, but just want to debug the source code without any errors, you can set it directly tosource-map, but this will cause two problems:

  • source-mapIn the mode, the Source Map with the highest quality and the most detailed will be output, which will cause the construction speed to be slow, especially increasing the waiting time when the development process needs frequent modification.
  • source-mapIn the mode, the Source Map will be exposed. if the Source Map that constructs the code to be published online is exposed, the source code will be leaked.

In order to solve the above two problems, can do this:

  • In the development environmentdevtoolset atcheap-module-eval-source-mapBecause the Source Map is generated at the fastest speed, it can accelerate the construction. Since code compression will not be done in the development environment, breakpoint debugging will not be affected even if there is no column information in the Source Map.
  • In the production environmentdevtoolset athidden-source-map, which means to generate the most detailed Source Map, but not to expose Source Map. Due to code compression in a production environment, a JavaScript file has only one line, so column information is required.

In a production environment, the Source Map is usually not uploaded to the HTTP server for users to obtain, but uploaded to the JavaScript error collection system, where the source code of the error is calculated according to the Source Map and the collected JavaScript running error stack.

Do not use in production environmentinlineThe Source Map of the schema, because it will make JavaScript files very large and will reveal the source code.

Load existing Source Map

Some third-party modules installed from Npm are written using ES6 or TypeScript, and they will bring the compiled JavaScript file and the corresponding Source Map file when they are released, so that you can debug them when you have problems using them.

By default, the Webpack will not load these additional Source Map files, and the Webpack will only generate Source Map during the conversion process. In order for the Webpack to load these additional Source Map files, installation is required.source-map-loader. The usage method is as follows:

module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        // 只加载你关心的目录下的 Source Map,以提升构建速度
        include: [path.resolve(root, 'node_modules/some-components/')],
        use: ['source-map-loader'],
        // 要把 source-map-loader 的执行顺序放到最前面,如果在 source-map-loader 之前有 Loader 转换了该 JavaScript 文件,会导致 Source Map 映射错误
        enforce: 'pre'
      }
    ]
  }
};


Due tosource-map-loaderIt takes a lot of computation when loading Source Map, so avoid to let the Loader process too many files, otherwise it will lead to slow construction speed. Usually usedincludeTo hit only concerned documents.

Reinstall the newly introduced dependencies:

npm i -D source-map-loader

After restarting the Webpack, you can debug it in the browser.node_modules/some-components/The source code in the catalog.

Actual combat summary

In practical applications, there will be various requirements. Although the previous section has given solutions to most scenario requirements, it is still difficult to cover all possibilities. Therefore, you need to have the ability to analyze the problems you encounter and then find the corresponding solutions. You can analyze and solve the problems according to the following ideas:

  1. We should understand the problems we are facing. For example, when building React applications with Webpack, you need to master the basic knowledge of React first.
  2. Find out the difference between reality and goal. For example, JSX grammar and ES6 grammar are used in the source code of React application, and the source code needs to be converted into ES5.
  3. Find out the possible path from reality to goal. For example, converting the new syntax to ES5 can use Babel to convert the source code.
  4. Is there a ready-made Webpack integration solution for possible paths in the search community? For example, they already exist in the community.babel-loader.
  5. If you can’t find a ready-made solution that shows your needs are very special, then you need to write your own Loader or Plugin.

In the process of solving the problem, it is important to have the following 2 abilities:

  1. From a knowledge you need to associate as much as possible with its related knowledge, which is helpful to get through your knowledge system and get the answer faster from experience.
  2. Be good at using search engines to find the problems you are facing, which is helpful to get the answers faster with the help of other people’s experience, rather than re-exploring yourself.

The most important thing is that you need more actual combat and solve the problem yourself, which is conducive to deepening your influence and understanding, instead of just looking at and not doing nothing.