webpack 入门

基本安装

新建项目文件夹、初始化 npm 以及在本地安装 webpack

mkdir webpack-demo && cd webpack-demo
npm init -y
npm install webpack webpack-cli --save-dev

新建目录

 webpack-demo
+ |- /dist
+   |- index.html
+ |- /src
+   |- index.js
  |- package.json

以下提到的文件,如果没有特殊说明(如 ‘src/index.js’),则默认在根目录下。

dist/index.html

<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Webpack Sample Project</title>
  </head>
  <body>
    <div id='app'>
    </div>
    <script src="bundle.js"></script>
  </body>
</html>

在 index.html 中将 bundle.js 引入。

src/index.js

document.getElementById('app').innerHTML = 'hello webpack';

新建 webpack 配置文件

touch webpack.config.js

当前目录结构:

 webpack-demo
  |- /dist
    |- index.html
  |- /src
    |- index.js
  |- package.json
+ |- webpack.config.js

下文中的所有配置,如未经特殊说明,均是在 webpack.config.js 文件中进行配置

设置 entry & output

webpack.config.js

const path = require('path');

module.exports = {
+   entry: {
+       main: './src/index',
+   },
+   output: {
+       path: path.resolve(__dirname, 'dist'),
+       filename: 'bundle.js'
+   }
}

  entry 是应用程序入口起点。程序从这里启动执行。
  output 指定在哪里输出打包好的文件。path 是输出文件的路径,filename 是输出文件名。
  path.resolve 是 nodejs 的一个方法,用于将路径转化为绝对路径。
__dirname 是 nodejs 的一个全局变量,表示当前文件的绝对路径值。

打包文件

使用 打包命令 打包一次文件

webpack --config webpack.config.js --mode development

上面这条命令指定了 webpack 配置文件为 webpack.config.js,环境为 development(开发)

然而,如果每次都输入这么长一串命令来执行打包,实在不方便,所以我们将这条命令写入 npm 脚本

package.json

{
    "name": "webpack-demo",
    "version": "1.0.0",
    "description": "",
    "scripts": {
      "test": "echo \"Error: no test specified\" && exit 1",
+     "build": "webpack --config webpack.config.js"
    },
    "keywords": [],
    "author": "",
    "license": "ISC",
    "devDependencies": {
      "webpack": "^4.0.1",
      "webpack-cli": "^2.0.9",
    }
  }

现在,我们可以通过 ‘npm run build’ 来执行打包命令。

下面这些命令行选项我们应该知道


webpack – building for development
webpack -p – building for production (minification)
webpack –watch – for continuous incremental building
webpack -d – including source maps
webpack –colors – making building output pretty

source map

开发离不开调试,通过调试我们能快速定位 bug 及时修复。当 webpack 打包源代码时,可能会很难追踪到错误和警告在源代码中的原始位置。

为了更容易地追踪错误和警告,JavaScript 提供了 source map 功能,将编译后的代码映射回原始源代码。

它允许我们设置通过何种方式编译源码,使得编译后的代码可读性更高,便于调试。

webpack.config.js

const path = require('path');

module.exports = {
    entry: {
        main: './src/index',
    },
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'bundle.js'
    },
+   devtool: 'eval-source-map'
}

现在,我们就能在浏览器控制台 ‘Sources’ 下看到我们的源码了。

loader

loader 用于对模块的源代码进行转换。loader 可以使你在 import 或”加载”模块时预处理文件。

CSS-loader

我们可以使用 loader 告诉 webpack 如何加载 CSS 文件,这里我们用到 style-loadercss-loader

webpack 允许我们在 js 文件中使用 css 文件,使用 css-loader 预处理 css 文件,通过 style-loader 将 <style> 标签插入 HTML。

npm install --save-dev style-loader css-loader

webpack.config.js

const path = require('path');

module: {
    entry: {
        main: './src/index',
    },
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'bundle.js'
    },
    devtool: 'eval-source-map',
+   module: {
+     rules: [{
+         test: /\.css$/,
+         use: ['style-loader','css-loader']
+     }]
+   }
}

  webpack 根据正则表达式,来确定应该查找哪些文件,并将其提供给指定的 loader。在这种情况下,以 .css 结尾的全部文件,都将被提供给 style-loader 和 css-loader。

我们使用 import ‘./style.css’ 引入样式后,该 CSS 文件将被作为内部样式插入到 html 文件的 中。

我们尝试一下,在项目中添加一个 style.css 文件,并将其导入我们的 index.html 中

main.css

.hello {
    color: red;
}

index.js

import './main.css'

document.getElementById('app').innerHTML = 'hello webpack';

function component() {
    var element = document.createElement('div');
    element.innerHTML = '<h1>hello</h1>';
    element.classList.add('hello');

    return element;
  }

document.body.appendChild(component());

现在打包一下程序,webpack 会在 index.html 中插入一个内部样式。

<head>
    <style type="text/css">.hello {
        color:red;
    }</style>
</head>

CSS Modules

想象这样一个场景:我们在两个页面中各有一个模块都作 “title” 使用,但是我们想给他们设置不同的样式。假如我们都把它们的 class 都设置为 “title”,那么就会造成命名空间污染,我们必须要为它们设置不同的 className。有两个还好办,假如有 5 个、10 个呢?要起多少个不同的名字呢?

CSS Modules 就是来解决这个问题的,它可以为每一个 JS 文件创造局部作用域,来避免全局 CSS 类名重复问题。意味着,我们可以在不同的 JS 文件中使用相同的类名。

CSS Modules 基于 css-loader 插件,修改配置文件

webpack.config.js

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    entry: {
        main: './src/index.js' // 程序入口
    },
    output: {
        path: path.resolve(__dirname, 'dist'), // 输出文件目录
        filename: 'bundle.js' // 输出文件
    },
    devtool: 'inline-source-map',
    module: {
        rules: [{
            test: /\.css$/,
-           use: ['style-loader', 'css-loader']
+           use: [{
+               loader: 'style-loader'
+           }, {
+               loader: 'css-loader',
+               options: {
+                   modules: true,
+                   importLoaders: 1,
+                   localIdentName: '[path][name]---[local]---[hash:base64:5]' // 指定css的类名格式
+               }
+          }]
        }]
    }
}

localIdentName 允许我们自己指定编译后的类名格式。
[path]:当前路径
[name]:当前 CSS 文件名
[local]:当前局部作用域类名
[hash:bash64:5]:编码格式

现在我们修改一下代码。

我们添加一个 hello.js 组件,将 main.css 引入

hello.js

import styles from './main.css'
export default {
    component() {
        var element = document.createElement('div');
        element.innerHTML = `<h1 class="${styles.hello}">hello component</h1>`;

        return element;
    }
}

在 index.js 中使用 hello.js 组件。

index.js

import hello from './hello';

document.getElementById('app').innerHTML = 'hello webpack';

document.body.appendChild(hello.component());

现在可以看到 hello.js 组件中的 <h1> 的 class 变为了 “src-main—hello—3rVhM”,正是我们定义的格式。

关于 CSS Modules,阮一峰老师有一篇教程讲的很详细:CSS Modules 用法教程

plugins

插件(plugins)是 webpack 的支柱功能,它们会在整个构建过程中生效,执行相关的任务。插件目的在于解决 loader 无法实现的其他事。

HtmlWebpackPlugin

HtmlWebpackPlugin 插件用于生成一个自动引用我们打包后的 JS 文件 html 文件,简单来说就是生成一个 html 文件,该 html 文件会自动将打包后的 JS 文件插入其中。

安装插件

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

webpack.config.js

const path = require('path');
+ const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    devtool: 'eval-source-map',
    entry: {
      app: './src/index.js',
      print: './src/print.js'
    },
    module: {
        rules: [{
            test: /\.css$/,
            use: ['style-loader', 'css-loader']
        }]
    },
+   plugins: [
+     new HtmlWebpackPlugin({
+       filename: 'index.html',
+       template: path.join(__dirname, 'src/index.html')
+     })
+   ],
    output: {
      filename: '[name].bundle.js',
      path: path.resolve(__dirname, 'dist')
    }
};

  filename 是输出文件名。
  template 是指定一个 html 模板,生成的 html 文件以 template 指定文件为模板。
  更多配置

MiniCssExtractPlugin

到目前为止,我们的 CSS 样式还是通过 <style> 标签放在 <head> 下的,假如我们想把每一个 CSS 文件分离出来独立引用,该怎么做呢?需要借助 MiniCssExtractPlugin 这个插件。

安装

npm install --save-dev mini-css-extract-plugin

webpack.config.js

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
+ const MiniCssExtractPlugin = require("mini-css-extract-plugin");

module.exports = {
    entry: {
        main: './src/index.js' // 程序入口
    },
    output: {
        path: path.resolve(__dirname, 'dist'), // 输出文件目录
        filename: 'bundle.js' // 输出文件
    },
    devtool: 'inline-source-map',
    module: {
        rules: [{
            test: /\.css$/,
            use: [{
-               loader: 'style-loader'
-           }, {
+               loader: MiniCssExtractPlugin.loader,
+               options: {
+                   publicPath: './'
+               }
            }, {
                loader: 'css-loader',
                options: {
                    modules: true, // 指定启动 css modules
                    importLoaders: 1,
                    localIdentName: '[path][name]---[local]---[hash:base64:5]' // 指定css的类名格式
                }
            }]
        }]
    },
    plugins: [
        new HtmlWebpackPlugin({
            filename: 'index.html',
            template: path.join(__dirname, 'src/index.html')
        }),
+       new MiniCssExtractPlugin({
+           filename: "[name].css"
+       })
    ]
}

现在,我们可以看到,CSS 文件被作为 <link> 独立引进来了。

webpack-dev-server

假如我们每次代码发生变化时,都需要手动执行 ‘npm run build’的话,实在是太麻烦了。

我们希望我们的代码发生变化时,可以被自动实时编译。这就可以使用 webpack-dev-server。

安装

npm install --save-dev webpack-dev-server

修改配置文件

webpack.config.js

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    entry: {
      app: './src/index.js',
      print: './src/print.js'
    },
    output: {
      filename: '[name].bundle.js',
      path: path.resolve(__dirname, 'dist')
    },
    devtool: 'eval-source-map',
    module: {
        rules: [{
            test: /\.css$/,
            use: ['style-loader', 'css-loader']
        }]
    },
    plugins: [
      new HtmlWebpackPlugin({
        filename: 'index.html',
        template: path.join(__dirname, 'src/index.html')
      })
    ],
+   devServer: {
+       contentBase: './dist'
+       host: 'localhost', // 默认是localhost
+       port: 3000, // 端口
+       open: true, // 自动打开浏览器
+   }
};

contentBase:默认webpack dev server是从项目的根目录提供服务,如果要从不同的目录提供服务,可以通过contentBase来配置,比如可以把contentBase配置成’./public’。

在 package.json 中配置一条命令

package.json

{
  "name": "webpack-react-demo",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "test",
    "dev": "webpack --config webpack.config.js --mode development",
+   "server": "webpack-dev-server --open"
  },
  "author": "jaakko",
  "license": "ISC",
  "dependencies": {},
  "devDependencies": {
    "css-loader": "^2.0.0",
    "html-webpack-plugin": "^3.2.0",
    "style-loader": "^0.23.1",
    "webpack": "^4.27.1",
    "webpack-cli": "^3.1.2",
    "webpack-dev-server": "^3.1.10"
  }
}

现在,运行 ‘npm run server’ 就可以看到效果:当我们的源码发生变化时,web 服务器就会自动重新加载编译后的代码。

注意!!!
在配置文件发生变化时,还是需要重新运行程序,否则配置不会生效

除了 webpack-dev-server,还有另外两种方式,可以帮助我们在代码发生变化后自动编译代码。
查看

模块热替换

模块热替换(Hot Module Replacement 或 HMR),它允许在运行时更新各种模块,而无需进行完全刷新。

启动热替换比较简单,只需要引入一个 webpack 内置插件,更新 webpack-dev-server 设置

webpack.config.js

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
+ const webpack = require('webpack');

module.exports = {
    entry: {
        main: './src/index.js' // 程序入口
    },
    output: {
        path: path.resolve(__dirname, 'dist'), // 输出文件目录
        filename: 'bundle.js' // 输出文件
    },
    devtool: 'inline-source-map',
    module: {
        rules: [{
            test: /\.css$/,
            use: [
                //     {
                //     loader: 'style-loader'
                // }, 
                {
                    loader: MiniCssExtractPlugin.loader,
                    options: {
                        publicPath: '../'
                    }
                }, {
                    loader: 'css-loader',
                    options: {
                        modules: true, // 指定启动 css modules
                        importLoaders: 1,
                        localIdentName: '[path][name]---[local]---[hash:base64:5]' // 指定css的类名格式
                    }
                }
            ]
        }]
    },
    plugins: [
        new HtmlWebpackPlugin({
            filename: 'index.html',
            template: path.join(__dirname, 'src/index.html')
        }),
        new MiniCssExtractPlugin({
            filename: "[name].css",
            chunkFilename: "[id].css"
        }),
+       new webpack.HotModuleReplacementPlugin() // 热更新
    ],
    devServer: {
        contentBase: './dist',
        host: 'localhost', // 默认是localhost
        port: 3000, // 端口
        open: true, // 自动打开浏览器
+       hot: true // 开启热更新
    }
}

此时已经完成了配置,但实际上并不会生效,我们还需要在入口文件加入以下

index.js

if (module.hot) {
    // 实现热更新
    module.hot.accept();
}

这里有一个小问题,我再设置了热更新后修改 css 文件不会自动更新模块,具体原因还未知,还在寻找中…

至此,webpack 的基本配置就完成了。更多配置待探索…