从零开始搭建一个 Webpack 开发环境配置(附 Demo)

本文使用 Webpack 从零开始搭建一个开发环境的脚手架配置,在此做个记录,也方便以后使用。


前言

我的上一篇文章简单介绍了一下 Webpack 的一些核心概念和基本配置,需要了解的朋友可以先参考一下Webpack 的简单介绍

从 webpack v4.0.0 开始,可以不用引入一个配置文件。直接使用 webpack 命令就可进行打包。但是,一般我们需要进行更灵活的配置功能,所以本文我也创建一个 webpack 的配置文件,对webpack 的一些属性进行配置。

本文 Demo 地址

环境搭建

项目结构

首先我们创建一个目录,初始化 npm,然后 在本地安装 webpack,接着安装 webpack-cli(此工具用于在命令行中运行 webpack):

$ mkdir webpack-dev-demo && cd webpack-dev-demo

$ npm init -y

$ npm install webpack webpack-cli --save-dev
复制代码

project

  webpack-dev-demo
  |- package.json
  |- /public
    |- index.html
  |- /src
    |- index.js
复制代码

src/index.js

function component() {
    var element = document.createElement('div');

    element.innerHTML = 'Hello World';
  
    return element;
}
  
document.body.appendChild(component());
复制代码

public/index.js

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Webpack 开发环境配置</title>
</head>
<body>
    
</body>
</html>
复制代码

package.json

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

复制代码

创建配置文件

在项目根目录下创建 webpack.config.js 配置文件

project

  webpack-dev-demo
  |- package.json
  |- /public
    |- index.html
  |- /src
    |- index.js
+ |- webpack.config.js
复制代码

配置入口和输出

webpack.config.js

const path = require('path');

module.exports = {
    entry: './src/index.js',
    output: {
        filename: '[name]-[hash:8].js',
        path: path.resolve(__dirname, 'dist')
    }
}
复制代码

运行 webpack

$ npm run build
复制代码

控制台的打印结果

第一次打印结果

可以看到打印日志,打包成功了,但是此时在浏览器打开我们的 index.html 文件,却发现界面上什么都不显示,这个也好理解,因为 index.html 此时还没有引入任何的 js 文件。所以这个时候就要将打包后的文件引入到 index.html 文件中,但是可以看到 dist 文件夹下的 js 文件名有很多的 hash 值,而且每次编译都可能不同,怎么办呢?这时候就要引入 html-webpack-plugin 插件了

html-webpack-plugin 插件的使用

安装插件:

$ npm install html-webpack-plugin --save-dev
复制代码

使用插件:

webpack.config.js

...
const HTMLWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    ...
    plugins: [
        new HTMLWebpack({
            // 用于生成的HTML文档的标题
            title: 'Webpack 开发环境配置',
            // webpack 生成模板的路径
            template: './public/index.html'
        })
    ]
}
复制代码

关于 html-webpack-plugin 插件更多配置请参考:插件文档

再次运行 webpack

$ npm run build
复制代码

可以看到 dist 文件夹下生成了一个 index.html 文件,在浏览器中打开这个 index.html 文件,可以看到,'Hello World' 已经能够正常显示了

至此,项目能够正常打包了,但是还不够,此时可以看到 dist 文件夹下有两个 js 文件,但是明明只打了一个包啊。是因为另一个包使我们上一次操作打出来的,并没有删除掉。所以,为了避免 dist 文件夹中的文件变得杂乱,我们还需要引入 clean-webpack-plugin 插件帮助我们清理 dist 文件夹

clean-webpack-plugin 插件的使用

安装插件:

$ npm install clean-webpack-plugin --save-dev
复制代码

用法:new CleanWebpackPlugin(paths [, {options}])

webpack.config.js

...
const CleanWebpackPlugin = require('clean-webpack-plugin');

module.exports = {
    ...
    plugins: [
        ...,
        // 用法:new CleanWebpackPlugin(paths [, {options}])
        new CleanWebpackPlugin(['dist'])
    ]
}
复制代码

再次运行 webpack

$ npm run build
复制代码

此时 dist 文件夹下只有一个 js 和 html 文件了。说明插件配置成功了,关于 clean-webpack-plugin 更多配置请参考:插件文档

配置 Http 服务并进行实时预览

安装 webpack-dev-server 包:

$ npm install --save-dev webpack-dev-server
复制代码

使用:

webpack.config.js

...
const webpack = require('webpack');

module.exports = {
    ...
    devServer: {
        // 必须配置的选项,服务启动的目录,默认为跟目录
        contentBase: './dist',
        // 使用热加载时需要设置为 true
        hot: true,
        /**
         * 下面为可选配置
         */
        // 指定使用一个 host。默认是 localhost
        host: 'localhost',
        // 端口号
        port: 8000,
        // 当使用 HTML5 History API 时,任意的 404 响应都可能需要被替代为 index.html。通过设置为 true 进行启用
        historyApiFallback: {
            disableDotRule: true
        },
        // 出现错误时是否在浏览器上出现遮罩层提示
        overlay: true,
        /**
         * 在 dev-server 的两种不同模式之间切换
         *   默认情况下,应用程序启用内联模式 inline
         *   设置为 false,使用 iframe 模式,它在通知栏下面使用 <iframe> 标签,包含了关于构建的消息
         */
        inline: true,
        /**
         * 统计信息,枚举类型,可供选项:
         *      "errors-only": 只在发生错误时输出
         *      "minimal": 只在发生错误或有新的编译时输出
         *      "none": 没有输出
         *      "normal": 标准输出
         *      "verbose": 全部输出
         */
        stats: "errors-only",
        // 设置接口请求代理,更多 proxy 配置请参考 https://github.com/chimurai/http-proxy-middleware#options
        proxy: {
            '/api/': {
                changeOrigin: true,
                // 目标地址
                target: 'http://localhost:3000',
                // 重写路径
                pathRewrite: {
                    '^/api/': '/'
                }
            }
        }
    },
    plugins: [
        ...,
        // 添加 NamedModulesPlugin,以便更容易查看要修补(patch)的依赖,由于设置了 mode: 'development',所以这个插件可以省略
        // new webpack.NamedModulesPlugin(),
        // 进行模块热替换
        new webpack.HotModuleReplacementPlugin()
    ]
}
复制代码

启用热加载功能:(上面已经添加了) 1、在 devServer 配置中添加 hot: true 属性 2、在 plugins 中添加 new webpack.NamedModulesPlugin()new webpack.HotModuleReplacementPlugin()

在 package.json 中添加一个执行命令

package.json

...
"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack --config webpack.config.js",
+   "start": "webpack-dev-server"
}
...
复制代码

启动 Http 服务

执行命令:

$ npm run start
复制代码

可以看到控制台打印输出:

打印日志

打开浏览器,输入:http://localhost:8000/,可以看到浏览器中可以正常显示 Hello World。

模式配置

webpack 配置中有一个 mode 属性的配置,三个可选属性:

  • production 会将 process.env.NODE_ENV 的值设为 production。启用 FlagDependencyUsagePlugin, FlagIncludedChunksPlugin, ModuleConcatenationPlugin, NoEmitOnErrorsPlugin, OccurrenceOrderPlugin, SideEffectsFlagPlugin 和 UglifyJsPlugin。

  • development 会将 process.env.NODE_ENV 的值设为 development。启用 NamedChunksPlugin 和 NamedModulesPlugin。

  • none 不选用任何默认优化选项

这里我们配置的是开发环境,所以需要将 mode 设置为 development

webpack.config.js

...
module.exports = {
+   mode: 'development',
    ...
}
复制代码

启用调试工具 Source Map

此时项目能够正常运行,所以没有什么问题,但是现在我们修改一下,将 index.js 中的 return element 改成错误的 return ele。我们 F12 打开开发工具,可以看到控制台的错误提示,点进去却发现跟我们写的代码不一致,难以对错误的代码进行调试,此时 Source Map 就派上用场了。

在 webpack.config.js 中添加 devtool 属性

webpack.config.js

...
module.exports = {
    mode: 'development',
+   devtool: inline-source-map
    ...
}
复制代码

devtool 的多个属性之间的差异

devtool 构建速度 重新构建速度 生产环境 品质(quality)
(none) +++ +++ yes 打包后的代码
eval +++ +++ no 生成后的代码
cheap-eval-source-map + ++ no 转换过的代码(仅限行)
cheap-module-eval-source-map o ++ no 原始源代码(仅限行)
eval-source-map -- + no 原始源代码
cheap-source-map + o yes 转换过的代码(仅限行)
cheap-module-source-map o - yes 原始源代码(仅限行)
inline-cheap-source-map + o no 转换过的代码(仅限行)
inline-cheap-module-source-map o - no 原始源代码(仅限行)
source-map -- -- yes 原始源代码
inline-source-map -- -- no 原始源代码
hidden-source-map -- -- yes 原始源代码
nosources-source-map -- -- yes 无源代码内容

再次运行项目:

$ npm run start
复制代码

可以看到报错依旧,但是在开发工具的控制台上,查看错误提示,可以根据提示清楚的找到我们写的代码所在位置.

测试之后请将错误的 return ele 改为正确的 return element

为项目添加模块解析规则

此时,开发环境已经配置的差不多了,但是我现在想给 div 加一个样式,想让文字编程蓝色,居中显示,那么此时就需要用的 loader 了,因为 webpack 默认无法解析 css,所以就需要我们自己配置了

配置 css 模块解析

安装所需插件:

$ npm install css-loader style-loader --save-dev
复制代码

css-loader 用来解析 css 文件,而 style-loader 则用来将解析好的 css 内容注入到 JavaScript 里。由于 loader 执行顺序是从下到上,所以要将 css-loader 写在下面。

使用:

webpack.config.js

...
module.exports = {
    ...
    plugins: [...],
    module: {
        rules: [
            {
                test: /\.css$/,
                use: [
                    'style-loader',
                    // 还可以给 loader 添加一些配置
                    {
                        loader: 'css-loader',
                        options: {
                            // 开启 sourceMop
                            sourceMap: true
                        }
                    }
                ]
            }
        ]
    }
}
复制代码

在 src 目录下新建一个 css 文件

src/index.css:

div {
    color: blue;
    text-align: center;
}
复制代码

src/index.js

require('./index.css');

...
复制代码

重新运行项目:

$ npm run start
复制代码

可以看到浏览器上此时文字颜色已经变蓝,并且居中显示。

配置其他模块解析

除了 css 之外,其他文件在 webpack 也都被认为是一个模块,也都需要对应的 loader 进行解析。 下面就不一一演示了,先把代码贴出来看一看。

下载所有需要的插件:

$ npm install file-loader csv-loader xml-loader html-loader markdown-loader --save-dev
复制代码

webpack.config.js

...
module.exports = {
    ...
    plugins: [...],
    module: {
        rules: [
            ...
            // 解析图片资源
            {
                test: /\.(png|svg|jpg|gif)$/,
                use: [
                    'file-loader'
                ]
            },
            // 解析 字体
            {
                test: /\.(woff|woff2|eot|ttf|otf)$/,
                use: [
                    'file-loader'
                ]
            },
            // 解析数据资源
            {
                test: /\.(csv|tsv)$/,
                use: [
                    'csv-loader'
                ]
            },
            // 解析数据资源
            {
                test: /\.xml$/,
                use: [
                    'xml-loader'
                ]
            },
            // 解析 MakeDown 文件
            {
                test: /\.md$/,
                use: [
                    "html-loader", 
                    "markdown-loader"
                ]
            }
        ]
    }
}
复制代码

这些配置基本上可以满足常规开发中使用到的各种模块资源,不过在开发过程中可能还会需要用到 less、scss 等 css 预编译语言,还需要使用 less-loader,sass-loader 进行配置。更多配置用法这里也无法一一详述,等大家用到的时候再去查阅对应文档即可。

使用 babel 进行配置

目前项目可以正常运行,但是现在 ES6、7 语法已经出来了,但是浏览器中还不能完全识别,所以我们需要 babel 讲 js 文件转换成浏览器可以识别的 ES5 语法。

安装 bable-loader 插件

$ npm install babel-core babel-loader --save-dev
复制代码

babel 还能进行配置,可以像上面那样直接在 loader 中进行配置,也可以在根目录下创建 .babelrc 文件配置,项目运行会自动从此文件中读取

使用 babel-loader:

webpack.config.js

...
module.exports = {
    ...
    plugins: [...],
    module: {
        rules: [
            {
                test: /\.js/,
                include: path.resolve(__dirname, 'src'),
                loader: 'babel-loader?cacheDirectory',
            },
        ]
    }
}
复制代码
babel 的 cacheDirectory 属性

默认值为 false。当有设置时,指定的目录将用来缓存 loader 的执行结果。之后的 webpack 构建,将会尝试读取缓存,来避免在每次执行时,可能产生的、高性能消耗的 Babel 重新编译过程。如果设置了一个空值 (loader: 'babel-loader?cacheDirectory') 或者 true (loader: babel-loader?cacheDirectory=true),loader 将使用默认的缓存目录 node_modules/.cache/babel-loader,如果在任何根目录下都没有找到 node_modules 目录,将会降级回退到操作系统默认的临时文件目录。

使用 cacheDirectory 选项,将 babel-loader 提速至少两倍。

babel 的 Presets 配置

presets 属性告诉 Babel 要转换的源码使用了哪些新的语法特性,一个 Presets 对一组新语法特性提供支持,多个 Presets 可以叠加。 Presets 其实是一组 Plugins 的集合,每一个 Plugin 完成一个新语法的转换工作。Presets 是按照 ECMAScript 草案来组织的,通常可以分为以下三大类:

  • 已经被写入 ECMAScript 标准里的特性,由于之前每年都有新特性被加入到标准里,所以又可细分为:

    • es2015 包含在2015里加入的新特性;
    • es2016 包含在2016里加入的新特性;
    • es2017 包含在2017里加入的新特性;
    • env 包含当前所有 ECMAScript 标准里的最新特性。
  • 被社区提出来的但还未被写入 ECMAScript 标准里特性,这其中又分为以下四种:

    • stage0 只是一个美好激进的想法,有 Babel 插件实现了对这些特性的支持,但是不确定是否会被定为标准;
    • stage1 值得被纳入标准的特性;
    • stage2 该特性规范已经被起草,将会被纳入标准里;
    • stage3 该特性规范已经定稿,各大浏览器厂商和 Node.js 社区开始着手实现;
    • stage4 在接下来的一年将会加入到标准里去。
  • 为了支持一些特定应用场景下的语法,和 ECMAScript 标准没有关系,例如 babel-preset-react 是为了支持 React 开发中的 JSX 语法。

babel 的 Plugins 配置

plugins 属性告诉 Babel 要使用哪些插件,插件可以控制如何转换代码。

安装项目中需要使用的 Presets 插件

$ npm install babel-preset-env babel-preset-stage-0 --save-dev
复制代码

安装项目中需要的 babel Plugin

$ npm install babel-plugin-transform-class-properties babel-plugin-transform-runtime babel-runtime --save-dev
复制代码
  • babel-plugin-transform-class-properties 可以在项目中使用新增的 class 属性用法

  • babel-plugin-transform-runtime 由于 babel 转换文件时会在每个文件中都写入辅助代码,使用此插件可以直接使用 babel-runtime 中的代码进行转换,避免代码冗余。所以 babel-plugin-transform-runtime 和 babel-runtime 成对使用

.babelrc

{
    "presets": ["env", "stage-0"],
    "plugins": [
        "transform-runtime",
        "transform-class-properties"
    ]
}
复制代码

配置完成,此时就可以在项目中自由的使用 ES6 等新增 js 语法了。

使用 friendly-errors-webpack-plugin

有时候项目提示错误,可能是编译错误,可能是 ESLint 提示错误等等,我们希望错误提示能够友好一些,就可以使用这个插件

插件安装:

npm install friendly-errors-webpack-plugin --save-dev
复制代码

使用:

webpack.config.js

...
const FriendlyErrorsWebpackPlugin = require('friendly-errors-webpack-plugin');

module.exports = {
    ...,
    plugins: [
        ...
        new FriendlyErrorsWebpackPlugin()
    ]
}
复制代码

更多配置请参考插件文档

配置模块如何解析 resolve

开发的时候我们经常会需要引入自己写的文件模块,可能会需要按照路径一级一级的找,这个时候我们就可以配置 resolve,为一些常用的路径设置别名

配置:

webpack.config.js

...
module.exports = {
    ...
    plugins: [...],
    modules: {...},
    resolve: {
        alias: {
            src: path.resolve(__dirname, 'src')
        }
    }
}
复制代码

使用: 无论在任何文件里,引入 src 目录下的 index.css 文件时,路径都可以按照下面的这个引入路径来写

index.js

- require('./index.css');
+ import 'src/index.css';
复制代码

重新运行项目,发现项目正常启动,index.css 中的样式也正常生效

至此,一个简单的开发环境的 Webpack 脚手架搭建完成了。

最终的项目结构以及文件代码

project

  webpack-dev-demo
  |- package.json
  |- /public
    |- index.html
  |- /src
    |- index.js
    |- index.css
复制代码

public/index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Webpack 开发环境配置</title>
</head>
<body>
    
</body>
</html>
复制代码

src/index.js

import 'src/index.css';

function component() {
    var element = document.createElement('div');

    element.innerHTML = 'Hello World';
  
    return element;
  }
  
document.body.appendChild(component());
复制代码

src/index.css

div {
    color: blue;
    text-align: center;
}
复制代码

.babelrc

{
    "presets": ["env", "stage-0"],
    "plugins": [
        "transform-runtime",
        "transform-class-properties"
    ]
}
复制代码

webpack.config.js

const path = require('path');
const HTMLWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const webpack = require('webpack');

module.exports = {
    mode: 'development',
    devtool: 'inline-source-map',
    entry: './src/index.js',
    output: {
        filename: '[name]-[hash:8].js',
        path: path.resolve(__dirname, 'dist')
    },
    devServer: {
        // 必须配置的选项,服务启动的目录,默认为跟目录
        contentBase: './dist',
        // 使用热加载时需要设置为 true
        hot: true,
        /**
         * 下面为可选配置
         */
        // 指定使用一个 host。默认是 localhost
        host: 'localhost',
        // 端口号
        port: 8000,
        // 当使用 HTML5 History API 时,任意的 404 响应都可能需要被替代为 index.html。通过设置为 true 进行启用
        historyApiFallback: {
            disableDotRule: true
        },
        // 出现错误时是否在浏览器上出现遮罩层提示
        overlay: true,
        /**
         * 在 dev-server 的两种不同模式之间切换
         *   默认情况下,应用程序启用内联模式 inline
         *   设置为 false,使用 iframe 模式,它在通知栏下面使用 <iframe> 标签,包含了关于构建的消息
         */
        inline: true,
        /**
         * 统计信息,枚举类型,可供选项:
         *      "errors-only": 只在发生错误时输出
         *      "minimal": 只在发生错误或有新的编译时输出
         *      "none": 没有输出
         *      "normal": 标准输出
         *      "verbose": 全部输出
         */
        stats: "errors-only",
        // 设置接口请求代理,更多 proxy 配置请参考 https://github.com/chimurai/http-proxy-middleware#options
        proxy: {
            '/api/': {
                changeOrigin: true,
                // 目标地址
                target: 'http://localhost:3000',
                // 重写路径
                pathRewrite: {
                    '^/api/': '/'
                }
            }
        }
    },
    plugins: [
        new HTMLWebpackPlugin({
            // 用于生成的HTML文档的标题
            title: 'Webpack 开发环境配置',
            // webpack 生成模板的路径
            template: './public/index.html'
        }),
        // 用法:new CleanWebpackPlugin(paths [, {options}])
        new CleanWebpackPlugin(['dist']),
        // 添加 NamedModulesPlugin,以便更容易查看要修补(patch)的依赖,由于设置了 mode: 'development',所以这个插件可以省略
        // new webpack.NamedModulesPlugin(),
        // 进行模块热替换
        new webpack.HotModuleReplacementPlugin()
    ],
    module: {
        rules: [
            {
                test: /\.js/,
                include: path.resolve(__dirname, 'src'),
                loader: 'babel-loader?cacheDirectory'
            },
            // 解析 css
            {
                test: /\.css$/,
                include: path.resolve(__dirname, 'src'),
                use: [
                    'style-loader',
                    // 还可以给 loader 添加一些配置
                    {
                        loader: 'css-loader',
                        options: {
                            // 开启 sourceMop
                            sourceMap: true
                        }
                    }
                ]
            },
            // 解析图片资源
            {
                test: /\.(png|svg|jpg|gif)$/,
                use: [
                    'file-loader'
                ]
            },
            // 解析 字体
            {
                test: /\.(woff|woff2|eot|ttf|otf)$/,
                use: [
                    'file-loader'
                ]
            },
            // 解析数据资源
            {
                test: /\.(csv|tsv)$/,
                use: [
                    'csv-loader'
                ]
            },
            // 解析数据资源
            {
                test: /\.xml$/,
                use: [
                    'xml-loader'
                ]
            },
            // 解析 MakeDown 文件
            {
                test: /\.md$/,
                use: [
                    "html-loader", 
                    "markdown-loader"
                ]
            }
        ]
    },
    resolve: {
        alias: {
            src: path.resolve(__dirname, 'src')
        }
    }
}
复制代码

package.json

{
  "name": "webpack-dev-demo",
  "version": "1.0.0",
  "description": "",
  "private": true,
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack --config webpack.config.js",
    "start": "webpack-dev-server"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "babel-core": "^6.26.3",
    "babel-loader": "^7.1.5",
    "babel-plugin-transform-class-properties": "^6.24.1",
    "babel-plugin-transform-runtime": "^6.23.0",
    "babel-preset-env": "^1.7.0",
    "babel-preset-stage-0": "^6.24.1",
    "babel-runtime": "^6.26.0",
    "clean-webpack-plugin": "^1.0.1",
    "css-loader": "^2.1.0",
    "html-webpack-plugin": "^3.2.0",
    "style-loader": "^0.23.1",
    "webpack": "^4.29.0",
    "webpack-cli": "^3.2.1",
    "webpack-dev-server": "^3.1.14"
  }
}
复制代码

源码地址

本文 Demo 地址


总结

本文篇幅较长,感谢各位的耐心阅读。本文主要从 入口、输出、插件(Plugins)、模块处理(Module)、loader、解析(resolve)等 6 个配置项着手配置了一个基本的 webpack 开发环境脚手架。本文主要讲解的内容为:

  • loader 的作用以及如何配置使用

  • babel 的作用以及配置项

  • 各个插件的功能以及适用场景

  • 解析能够为开发带来的效率

本文内容对于已经熟练掌握 Webpack 的朋友来说,可能有些浅薄,但是着重讲解了各个配置项的功能以及配置后对项目产生的效果。对于准备入门 webpack 的朋友应该会有一定的帮助。

猜你喜欢

转载自juejin.im/post/5c51520cf265da61180215e5
今日推荐