Writing in the front

Here’s the thing. I was working on a project while I was doing my senior project. This project was built with React, but without using the official CLI tools, it was built through a lot of torturing. After the project went online, the volume was too large, so it took 8s to load for the first time!! So I decided to learn webpack again and find an opportunity to optimize the package of this project.

Before also had some one-sided understanding of webpack, according to the bosses, write some blog refer to official document, set the project environment, and packaging to deploy, but always feel understanding of webpack is very obvious, and has recently found a video learning, do some simple share below (I think video is better than that of the official document writing, Official documents seem to have been ridiculed).

Webpack and modular development

With the in-depth development of the previous paragraph technology, the traditional HTML + JS + CSS a long-winded way is now not quite applicable. First of all, the introduction of a large number of JS files in HTML files is difficult to manage, which may cause the problem of variable pollution. Second, we may not need everything in a file to import it.

The original goal of WebPack was to achieve modular front-end projects, that is, how can it solve the problem of managing and maintaining every resource in a front-end project more efficiently

The history of modular development:

Stage-1 file partitioning

This was also one of the earliest forms of web modularization, where different files were placed in separate JS files and then imported using script tags

└ ─ stage -1├ ─ ─module- a. s ├ ─ ─module- b.j s └ ─ ─ index. HTMLCopy the code

Disadvantages:

  1. Modules work directly in the global scope, and a large number of module members pollute the global scope.
  2. There is no private space, and all module members can be accessed or modified outside the module.
  3. Once the number of modules increases, naming conflicts tend to occur.
  4. Unable to manage dependencies between modules
  5. It is also difficult to identify which module each member belongs to during maintenance

;

Stage-2 namespace mode

Later, we agreed that each module would expose only one global object, into which all members of that module would hang. You add a namespace to the internal members of each module

// module-a.js
window.moduleA = {
  method1function ({
    console.log('moduleA#method1')}}Copy the code

Disadvantages:

Nothing can be solved except the naming problem

Stage-3 IIFE executes the function immediately

The IIFE (Immediately-Invoked Function Expression) provides private space for the module. This is done by placing each module member in a private scope created by a function that executes immediately, and by hanging onto global objects for members that need to be exposed externally

// module-a.js; (function ({
  var name = 'module-a'
  function method1 (param{
    console.log(name + '#method1' + param)
  }
  window.moduleA = {
    Modulea.method1 (' DDDDD '); // Importing this file can be called via modulea.method1 (' DDDDD ')
    method1: method1
  }
})()
Copy the code

Stage 4-IIFE depends on parameters

In addition to IIFE, we can use IIFE parameters as dependency declarations, which makes the dependencies between each module more obvious.

// module-a.js; (function ($// Clearly indicate the module's dependencies with parameters
  var name = 'module-a'
  function method1 ({
    console.log(name + '#method1'The $()'body').animate({ margin'200px'})}window.moduleA = {
    method1: method1
  }
})(jQuery)

Copy the code

Disadvantages:

The most obvious problem is module loading. In both cases, the organization of module code is addressed, but the loading of modules is ignored. We introduce modules directly into the page using script tags, which means that the loading of modules is not controlled by the code.

Appearance of modular specification CommonJS –> AMD –> ES Module

CommonJS: The CommonJS specification is the module specification that node.js follows. It states that a file is a module. Each module has a separate scope and exports members through module.exports. However, node.js actually loads modules at startup and only uses modules during execution, so using them in Node is fine. However, using synchronous loading on the browser side will lead to inefficient application running.

AMD: Because of the Synchronous loading mechanism of CommonJS, the early modular standard was designed specifically for browsers rather than the CommonJS specification. It is called the AMD specification. The asynchronous module definition specification. At the same time, a very famous library called require.js was released, which, in addition to implementing the AMD modularization specification, is itself a very powerful module loader.

ES Modules: ES Modules are a module system defined in ECMAScript 2015 (ES6). Has developed into the most mainstream front-end modular standard. Compared with the development specifications put forward by the community like AMD, ES Modules are modularization implemented at the language level, so its standard is more perfect and reasonable. ES Modules is a feature that most browsers are starting to support natively.

Problems to be solved in modularization

  1. ES Modules themselves are only supported by ES6, which has its own environmental compatibility issues. Although today’s major browsers work, there is no guarantee that users’ browsers will support it.
  2. There are too many module files divided by modularity, and the front-end application runs in the browser, so each file needs to be individually requested from the server. Fragmented module files inevitably cause the browser to send network requests frequently, affecting the efficiency of applications.
  3. In the front-end application development, not only JS code needs to be modular, HTML and CSS these resource files also face the problem of being modular. And from a macro perspective, these files are also part of the front-end application.

Webpack can help us solve the problem

1. Combined with the Babel plug-in, it helps us to compile code, that is, to convert the code we wrote during the development phase to include new features into code that can be compatible with most environments, and solve the environmental compatibility problems we face.

2. The ability to pack scattered modules back together eliminates the problem of frequent browser requests for module files. It is important to note that modularized file partitioning is only necessary during development because it helps us better organize the code, but not during actual run time.

3. Can support the impassability kinds of front-end module type, that is to say, can be involved in the development process of styles, pictures, fonts, etc all resource files are used as a module, so that we have a modular scheme with him, and all of the resource files load can be controlled through the code, and the integration of business code maintenance, a more reasonable.

Normal modular development vs. Webpack modular development

Common modular development

First we tried to do modular development using traditional development methods and see what the problems were.

└ ─01Es - modules ├ ─ ─ the SRC │ ├ ─ ─ index. The js │ └ ─ ─ main. Js └ ─ ─ index. The HTMLCopy the code
// ./src/index.js
import main from './main.js';

const testES_Module = main();

document.body.append(testES_Module);
Copy the code
// ./src/main.js
export default() = > {const ele = document.createElement('h2');
  ele.textContent = 'Hello World';
  ele.addEventListener('click'.() = > {console.log('clicked'); })return ele;
}
Copy the code
<! DOCTYPEhtml>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
  <title>Document</title>
</head>
<body>
</body>
<script src="src/index.js"> </script>
</html>
Copy the code

Let’s just open the index.html file for a previewA review of the ES-Modules MDN documentation shows that to use the modular approach to HTML, you need to use the<script>Tag with type=’module’ attribute

- <script src="src/index.js"> </script>
+ <script type="module" src="src/index.js"> </script>
Copy the code

Re-open, found unexpectedly cross domain!!When we looked at the MDN documentation again, we found this sentence, so we need to start a service. This section uses http-server as an example.

cnpm install -g http-server
Copy the code
// Run in the project directory
http-server -p 8888
Copy the code

As you can see, we used the ES Module specification for normal development without using WebPack. However, there are still many browsers that don’t support ES Modules, so we need tools like Webpack to help us solve the problem.

Webpack modular development

Initialize the project

npm init --yes
Copy the code

It is recommended to fix the webpack and WebPack-CLI versions directly in package.json for version consistency

{
  "name": "webpack"."version": "1.0.0"."description": ""."main": "index.js"."scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": ""."license": "ISC"."devDependencies": {
    "webpack": "^ 4.40.2"."webpack-cli": "^ 3.3.9"}}Copy the code
cnpm install
Copy the code

Create a webpack.config.js file at the root of the project to provide webpack configuration, telling WebPack how to package our files

// The built-in module of Node
const path = require('path');

module.exports = {
  mode: 'none'.// Mode is divided into production development node and set to node so that we can read the packaged files
  entry: './src/index.js'./ / the entry
  output: {
    // Output the file name
    filename: 'bundle.js'.// Output path
    path: path.join(__dirname, 'dist')}},Copy the code

Note: since our webpack.config.js file is a JS file running in node.js, which means we need to write the code in CommonJS style, this file can export an object, and we can configure the corresponding options through the properties of the exported object

Using NPX to run webpack in the current directory helps us pack

npx webpack
Copy the code

View the packaging results in the dist directoryOf course, we can view the results directly in a preview HTML file (however, we still recommend that you start a service to test in the future during the local testing process, on the one hand, it can solve the cross-domain problems, and on the other hand, it can improve our development experience by automatically refreshing and even HMR).

Loader mechanism in Webpack

Before learning Webpack, the cognition of loader was simply configuring a lot of loaders in module. I’ve been wondering, isn’t CSS supported by browsers? Why do YOU need CSS-loader, or even style-loader?

As mentioned earlier, WebPack is not just a JavaScript module packaging tool, but a module packaging tool for the entire front-end project. However, webpack can only handle JS module code by default. By default, it treats all encountered documents as JavaScript modules by default. Various loaders in Webpack are the main contributors to support webpack to support different types of resource module loading.

Assuming we don’t have any loaders configured so far, import the CSS file into the project

└ ─01- es - modules ├ ─ ─ the SRC │ ├ ─ ─ index. The js │ └ ─ ─ CSS, js └ ─ ─ index. The HTMLCopy the code
body {
  background-color: red;
}
Copy the code
import './index.css'

console.log('success');
Copy the code

Run the NPX webpackAn error.Webpack can only handle JS module code internally by defaultFor modules other than JS, we need additional loaders to help us handle them (.css files themselves are not modular), so csS-Loader comes naturally to us.

cnpm install -D css-loader
Copy the code

Webpack.config.js tells Webpack what we should do with.css:

const path = require('path');

/ * * *@type { import('webpack').Configuration }* /
module.exports = {
  mode: 'none'.entry: './src/index.js'.output: {
    filename: 'bundle.js'.path: path.join(__dirname, 'dist')},module: {
    rules: [{test: /\.css$/,
        use: 'css-loader'}}}]Copy the code

Add a Rules array to the Module property of the configuration object. This array is our load rule configuration for the resource module, where each rule object requires two properties:

The first is the test property, which is a regular expression that matches the path of the file encountered during packaging. Here we end with.css;

Then there is the use attribute, which specifies the loader to use for the matched file, in this case CSS-loader.

Re-run pack NPX webpack, success!Don’t get too excited. We created a new index.html file in the dist directory to reference our bundle.js bundle and found that the CSS properties we set didn’t take effect (for those of you who have configured Webpack, we need to have a style-loader). Let’s take a look at the packaging results so far.If you look carefully at this file, you will see that csS-Loader converts the CSS module into a JS module by pushing our CSS code into an array provided by a module within csS-Loader. But this array is not used anywhere in the process.

So here’s why the style doesn’t work: CSS-loader only loads the CSS module into the JS code, it doesn’t use the module.

Install style-loader and change use to an array

cnpm install -D style-loader
Copy the code
// webpack.config.js
const path = require('path');

/ * * *@type { import('webpack').Configuration }* /
module.exports = {
  mode: 'none'.entry: './src/index.js'.output: {
    filename: 'bundle.js'.path: path.join(__dirname, 'dist')},module: {
    rules: [{test: /\.css$/,
        use: [
          'style-loader'.'css-loader'}]}}Copy the code

When we look at the package, we see that there are two more modules and the CSS styles on the page are displayed properly. We can see the creation of the style tag in the code.All style modules loaded in THE CSS -loader are added to the page by creating style tags.

Developing a Loader

Suppose we need to develop a loader that loads markdown files so that we can import md files directly into our code. To do that, we need to install a package that converts md files to HTML –marked

cnpm install -D marked
Copy the code
└ ─04├─ ├─ ├─ ├─ ├─ ├─ webpack.config.jsCopy the code

// about.md# # # Webpack notes1.This is a Webpack note1
2.This is a Webpack note2
3.This is a Webpack note3
Copy the code
// ./markdown-loader.js
const marked = require('marked')
module.exports = source= > {
  // 1. Convert markdown to an HTML string
  const html = marked(source)

  // 2. To concatenate an HTML string into an exported string
  const code = `module.exports = The ${JSON.stringify(html)}`
  return code 
}
Copy the code
// index.js
import about from './about.md'
console.log(about);
Copy the code
const path = require('path');

module.exports = {
  mode: 'none'.entry: './src/index.js'.output: {
    filename: 'bundle.js'.path: path.join(__dirname, 'dist')},module: {
    rules: [{test: /\.md$/.// Use the module file path
        use: './markdown-loader.js'}}}]Copy the code

For loaders, there are a few things to note here:

  1. Use in webpack.config.js can use not only the module name, but also the module file path, just like require in Node
  2. Each Webpack Loader needs to export a function, which is the processing process of our Loader for resources. Its input is the content of the loaded resource file, and the output is the result after processing (return).
  3. The process of Webpack loading resource files is similar to a working pipeline. You can use multiple Loaders in turn, but the end result of the pipeline must be a standard JS code string.

Plugin mechanism in Webpack

Loader is responsible for loading a variety of resource modules in the project, so as to achieve the modularization of the overall project, while Plugin is used to solve the project in addition to resource module packaging other automation work, it can be said that Plugin has a wider range of capabilities, more uses.

Experience plugin mechanism

  1. Clean-webpack-plugin: Automatically cleans up the dist directory before packaging
  2. Html-webpack-plugin: Automatically generates THE HTML files required by the application
  3. Copy-webpack-plugin: Specify static files that do not need to be built. We want Webpack to copy all files in this directory to the output directory when it is packaged.

Install dependencies

cnpm install -D clean-webpack-plugin html-webpack-plugin copy-webpack-plugin
Copy the code

It is then used under the plugins property in webpack.config.js

// ./webpack.config.js
const path = require('path');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin')


module.exports = {
  mode: 'none'.entry: './src/main.js'.output: {
    filename: 'bundle.js'.path: path.join(__dirname, 'dist')},plugins: [
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      template: './src/index.html'
    }),
    new CopyWebpackPlugin({
      patterns: ['public'] // Wildcard of the directory or path to be copied]}}),Copy the code

Create a public folder in the root directory and add the text. TXT file. Create a new HTML template in the SRC directory

<! DOCTYPEhtml>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
  <title>Document</title>
</head>
<body>
  
</body>
<script></script>
</html>
Copy the code

Run pack command NPX webpack successfully!! As you can see, we don’t need to create our own HTML files to test our packed JS files, and the static files are not compiled and copied directly to the dist directory

Developing a plug-in

As mentioned earlier, plug-ins have a broader range of capabilities than Loaders, because loaders work only at the loading stage of modules, whereas plug-ins can touch almost every part of Webpack’s work.

There are many steps in the way Webpack works, and similar to events in Web applications (lifecycle functions in the framework), Webpack has a hook buried in almost every step. So we’re developing plug-in life by going to these different nodesMount different tasks, can achieve the purpose of expanding Webpack.Now, our requirement is to be able to see the comments in the Webpack result so that our bundle.js will be easier to read.

Create remove-comments-plugin.js in the project root directory

Webpack requires that our plug-in be either a function or an object that contains apply methods, and we typically define a type within which apply methods are defined. This type is then used to create an instance object to use the plug-in.

So we define a RemoveCommentsPlugin type, and then define an apply method in this type. This method is called when Webpack starts, and it takes a Compiler object parameter, which is the core object of the Webpack process. It contains all the configuration information for our build. It is through this object that we register the hook function, as shown below

class RemoveCommentsPlugin {
  apply(compiler) {
    console.log('RemoveCommentsPlugin start')}}Copy the code

With that in mind, we also need to know when to execute the task, which hook to mount the task on.

Our requirement is to remove the comments in bundle.js, which is only possible once the content of the bundle.js file that Webpack needs to generate is clear. Then we found the EMIT lifecycle function in the official Webpack hook function, and the comparison price met our requirements.

// ./remove-comments-plugin.js
class RemoveCommentsPlugin {
  apply(compiler) {
    compiler.hooks.emit.tap("RemoveCommentsPlugin".(compilation) = > {
      // compilation => can be understood as the context of this compilation

      for (const name in compilation.assets) {
        console.log(name); // Output the file name}}); }}module.exports = RemoveCommentsPlugin;
Copy the code
const path = require('path');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const RemoveCommentsPlugin = require('./remove-comments-plugin.js');


module.exports = {
  mode: 'none'.entry: './src/index.js'.output: {
    filename: 'bundle.js'.path: path.join(__dirname, 'dist')},plugins: [
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      title: 'My App'.template: './src/index.html'
    }),
    new RemoveCommentsPlugin(),
  ]
}
Copy the code

Run NPX webpack and see that our file name is actually printed

The next thing we need to do is first check if it’s a JS file, then get the context of the file, then remove the comments in the code by means of re substitution, and finally overwrite the corresponding properties in the compilation.assets. In the overwritten object, we return the original format.

class RemoveCommentsPlugin {
  apply(compiler) {
    compiler.hooks.emit.tap("RemoveCommentsPlugin".(compilation) = > {
      // compilation => can be understood as the context of this compilation
      for (const name in compilation.assets) {
        if (name.endsWith('.js')) {
          const contents = compilation.assets[name].source();
          const noComments = contents.replace(/\/\*{2,}\/\s? /g.' ');
          compilation.assets[name] = {
            source: () = > noComments,
            size: () = >noComments.length, } } } }); }}module.exports = RemoveCommentsPlugin;
Copy the code

This is how we implemented the removal of a commented plug-in by attaching task functions to hooks in the Webpack lifecycle.