使用webpack打造专属的模块化项目

2021/07/28 小雨

背景

为什么会使用到 webpack 呢?原因是这样的,最近在学习 Three.js 的过程中通过 script 标签引入相关依赖过于冗杂,使用起来也很不方便,如下所示:

<script src="../../../lib/jquery-1.12.min.js"></script>

<script src="../../../lib/three.min.js"></script>

<script src="../../../lib/libs/dat.gui.min.js"></script>
<script src="../../../lib/libs/inflate.min.js"></script>
<script src="../../../lib/libs/NURBSCurve.js"></script>

<script src="../../../lib/controls/OrbitControls.js"></script>
<script src="../../../lib/loaders/FBXLoader.js"></script>
<script src="../../../lib/libs/inflate.min.js"></script>

<script src="../../../lib/postprocessing/EffectComposer.js"></script>
<script src="../../../lib/postprocessing/ShaderPass.js"></script>
<script src="../../../lib/shaders/CopyShader.js"></script>
<script src="../../../lib/shaders/LuminosityHighPassShader.js"></script>
<script src="../../../lib/postprocessing/UnrealBloomPass.js"></script>
<script src="../../../lib/postprocessing/RenderPass.js"></script>

<script src="../../../lib/renderers/CSS3DRenderer.js"></script>
<script src="../../../lib/libs/Tween.js"></script>

<script src="./js/seat.js"></script>

于是我就想在这种没有使用类似 vue 、react 这种工程化的项目中如何去使用 npm 来管理各种依赖,首先找到的方法是在 script 标签上加上 type="module" 以此来启用模块功能,然后就能使用 import 来引入依赖了,如下所示:

<script type="module">
	import * as Three from '../../node_modules/three/build/three.module.js' 
</script>

但是之后又出现了一个问题,当我使用import加载 three 相关插件的时候,浏览器会产生下面的报错:

Uncaught TypeError: Failed to resolve module specifier "three". Relative references must start with either "/", "./", or "../"

经过一番检查之后,发现原来通过 npm 安装的插件引入依赖时 import 使用的是工程化的路径,如果不想进入每个子模块插件修改相对路径,就只能使用打包工具来解决这一问题了。

webpack 相关概念

webpack 是一个用于 JavaScript 应用程序的静态模块打包工具。它可以将各类资源整合到一起,包括.css、.png、.js等文件,然后“打包”生成一个新的 bundle 文件。新的 js 文件仅包含使用到的依赖,并且可压缩代码,提高运行速度。

webpack 有这么几个主要的概念:entryoutputloaderpluginmode

webpack:entry

entry 用于在配置文件中配置打包入口。常用格式如下:

module.exports = {
	entry: './entry/file.js'
};
webpack:output

output用于在配置文件中配置打包后的输出位置。常用格式如下:

path 表示输出文件的绝对路径, __dirname 表示该配置文件所在的绝对路径,filename表示输出的新文件名,path.resolve() 会将括号内的参数解析成一个完整的路径,如下会打包文件就会输出到 __dirname/dist/my-first-webpack.bundle.js

const path = require('path');

module.exports = {
    entry: './entry/file.js',
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'myFirst.bundle.js'
    }
};
webpack:loader

loader 主要用以使 webpack 能够加载打包非 js 文件。所有的 loader 可以在官网查看。常用格式如下:

npm install --save-dev style-loader css-loader
const path = require('path');

module.exports = {
    entry: './entry/file.js',
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'myFirst.bundle.js'
    },
    module: {
        rules: [{
            test: /\.css$/,
            use: ['style-loader', 'css-loader']
        }]
    }
};
webpack:plugin

plugin 插件功能是 webpack 的支柱功能,主要是为了解决 loader 无法实现的其他情况,例如 HtmlWebpackPlugin 插件会在打包完成后自动生成一个 html 文件并且引入对应的 bundle 。常用格式如下:

npm install --save-dev html-webpack-plugin
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    entry: './entry/file.js',
    plugins: [
        new HtmlWebpackPlugin(),
    ],
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'myFirst.bundle.js'
    },
    module: {
        rules: [{
            test: /\.css$/,
            use: ['style-loader', 'css-loader']
        }]
    }
};
webpack:mode

mode 用来在配置文件中指定环境: development & production 。 webpack 在不同环境下会加载不同的插件和处理方式。

module.exports = {
   mode: "production",
}
module.exports = {
  mode: "development",
}

webpack 使用方法

  1. 首先在项目里安装一下 webpack :

    npm install webpack webpack-cli --save-dev
    
  2. 创建配置文件 webpack.config.js ,具体配置内容可以参考上方相关概念

    const path = require('path');
    
    module.exports = {
        entry: 入口文件,
        output: {
            path: 出口文件目录,
            filename: 出口文件名
        },
        module: {
            rules: [
                各类loader配置项
            ]
        },
        plugins: [各类插件名],
    };
    
  3. 运行打包命令

    npx webpack --config webpack.config.js
    

    用 cli 这种方式来运行本地的 webpack 并不是特别方便,我们也可以在 package.json 中设置一个快捷方式

    "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1",
        "build": "webpack"
    },
    

    然后就可以使用如下的命令来启动 webpack 打包

    npm run build
    
开发模式实时重载

在开发过程中,如果我们修改一个地方就需要进行一次打包的话那样效率就太低了, webpack 也提供了一个本地服务 webpack-dev-server 具有实时重载的功能,具体使用方法也很简单,如下所示:

  1. 首先进行一下安装

    npm install --save-dev webpack-dev-server
    
  2. 修改配置文件,告知 dev server,从什么位置查找文件:

    关于模块热替换,可以前往官网了解,这里不做赘述

    module.exports = {
        devServer: {
            contentBase: './dist', // 文件对应位置
            hot: true  // 是否开启模块热替换
        }
    }
    
  3. 运行方式添加至 package.json

    "scripts": {
        "start": "webpack serve --open",
    },
    
  4. 运行

    npm start
    
定制自己的打包策略

以下内容需要先学习一下 webpack 的管理输出相关指南

webpack 默认的打包规则,只能存在一个入口一个出口,但是我这个是练习使用的 demo ,想要让打包出来的文件如下图所示:

使用webpack打造专属的模块化项目_第1张图片

于是经过一番搜索之后,发现了 glob 这个插件,它可以帮助我匹配文件夹中所有符合条件的文件,首先我先实现了多入口,代码如下所示:

npm i glob -D
const path = require('path');
const glob = require('glob');
module.exports = {
    mode: 'development',
    entry: glob.sync("./基础知识/*/*.js").reduce((entries, p) => {
        const name = path.basename(p, '.js') //获取路径的文件名 aaa/bbb.js => bbb
        return {
            ...entries,
            [name]: p
        }
    }, {}),
    ...
}

多出口可以通过 [name] 这个变量实现

output: {
    path: path.resolve(__dirname, "基础知识/dist"),
    filename: "[name]/[name].bundle.js",
},

同理我也可以通过插件 HtmlWebpackPlugin 为每个出口 bundle 配置对应的 html ,整体配置如下所示:

const path = require("path");
const glob = require("glob");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const EncodingPlugin = require("webpack-encoding-plugin");

module.exports = {
    mode: "development",
    entry: glob.sync("./基础知识/*/*.js").reduce((entries, p) => {
        const name = path.basename(p, ".js"); //获取路径的文件名 aaa/bbb.js => bbb
        return {
            ...entries,
            [name]: p,
        };
    }, {}),
    devtool: "inline-source-map",
    plugins: [
        ...glob.sync("./基础知识/*/*.js").reduce((entries, p) => {
            const name = path.basename(p, ".js"); //获取路径的文件名 aaa/bbb.js => bbb
            return [
                ...entries,
                new HtmlWebpackPlugin({
                    chunks: [name],
                    template: "./基础知识/" + name + "/" + name + ".html",
                    filename: name + "/" + name + ".html",
                }),
            ];
        }, []),
        new CleanWebpackPlugin(),
        new EncodingPlugin({
            encoding: "UTF-8",
        }),
    ],
    output: {
        path: path.resolve(__dirname, "基础知识/dist"),
        filename: "[name]/[name].bundle.js",
        clean: true,
    },
    module: {
        rules: [
            {
                test: /\.css$/,
                use: ["style-loader", "css-loader"],
            },
            {
                test: /\.(png|svg|jpg|gif)$/,
                use: ["file-loader"],
            },
            {
                test: /\.(woff|woff2|eot|ttf|otf)$/,
                use: ["file-loader"],
            },
        ],
    },
};

总结

我的原本主要目的就是想减少 script 标签冗杂的引入,却没想到使用 webpack 尝试了一下模块化项目的构建过程,学习的过程就是这么有趣。

你可能感兴趣的:(js,webpack)