Webpack 前端工程化

67 阅读3分钟

背景

随着前端业务的逐步复杂,早期的html js css 页面式的开发变得很难维护,随之象后端java 一样,需要类,需要面向对象,需要打包等一系列工程化手段来提高前端开发的复用性,可扩展性。由于java 后端 工程化较为成熟,所以后来的很多前端的工程化思路与java 类似.比如OOP 的type script.对应maven 的打包工具webpack。

因为工程化的需求,前端工程化发展因为没有标准,而走了很多弯路,比commonjs require等标准。其实都是在解决包的依赖问题。直接es6 将import export标准定义。

但这些标准定义之后又存在另一个问题,大家开发可以按新的标准开,但实际运行时的js 宿主,浏览器版本很验马上兼职新的标准。所以出现了编译器。也就是上边提到的

计算机领域有一句话:没有什么问题不是通过中间加层而得到解决的。

通过编译器将开发的源文件经过转换变成浏览器可以直接运行的脚本。

其实就是这样的一个函数

convert(es6)=es5 (早期浏览器兼容的版本)

版本的转换得到解决。但另一个工程化的问题,复用和扩展性。如何解决呢?

象java OOP 的类一样,高内聚低耦合,封装复用?所以组件化开发出现了。也就是后边我们的看到的reac 和vue。

所以组件化可以理解为前端发展的一次非常重要的变革。里程碑!

那么接下来的问题,如何将组件中的各种依赖,类似于java 中的import 包相关的依赖聚合成浏览器需要执行的html文件如何解决呢?

这就是本文要讲的webpack

Webpack 使用

webpack技术可以理解是前端得以发展的基石。所以现在react vue 等各种框架的打包工具都是是基于webpack。

实际解决的前端 各种资源的依赖关系。

下面我们通过 个小例 子分析webpack 的基本使用和原理,工程原文件如下:

bottom.html

<div>bottom</div>

header.html

<div> header</div>

heading.js

Export 导出一个函数 es语法

export function greet(name) {
    console.log(name);
    return `Hello ${name}`;
}

main.css

.main {
    background: red;
}

main.js

这里通过es 6语法引用各种资源 文件,包括js 图片 css 等等

import {greet} from "./layout/heading"; //引js
import './main.css' //引css
import logo from './logo.png' //引图片
greet("zhagnsan");
/**
* 一定有dom 使用的方法才会被导出,否则不预导出
*/
document.getElementById("clickButton").addEventListener('click', function () {
    alert(greet('zhangsan'));
    alert(greet);
});

export default  function (){
    greet("zhangsan");
}
document.getElementById("hello").classList.add("main");

const  image=new Image();
image.src=logo;
document.body.append(image);

index.html

html合并的模板文件

<html>
<head>
    <meta charset="UTF-8">

</head>
#include("./layout/header.html")
#include("./layout/bottom.html")

<div id="hello">
    Hello main
</div>

<input id="clickButton" type="button" value="点击我试一下">
</html>
因为闭包,这里无法执行,但事件是可以执行的的

<script>
    greet("hello");
</script>

依赖关系如下

暂时无法在飞书文档外展示此内容

我们的目标是将main.js 和index.html 合并成merge.html

上图中的html 片断和js 文件都可以理解成一个前端的组件,下面我们通过webpack 将各个组件合并成merge.html

提供给浏览器运行。

具体的配置如下

const path = require("path");
// 引入自动生成 html 的插件
const HtmlWebpackPlugin = require("html-webpack-plugin");

const {CleanWebpackPlugin} = require("clean-webpack-plugin");
// css 打包的插件
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = {
    optimization: {
        usedExports: false, // 只使用导出(意思是不使用的脚本不导出)
        sideEffects: false, // 禁用副作用检测(对于 package.json 中的 "sideEffects" 属性)
        minimize: false, // <---- 禁用 uglify. //禁用代码混淆
    },
    mode: "development", //开发模式
    //打包入口,原理是通过入口js 文件,逐步分析import,即所有的依赖递归树描述并进行转换
    //指定入口文件,程序从这里开始编译,__dirname当前所在目录, ../表示上一级目录, ./同级目录
    entry: {
        // main_css:path.resolve(__dirname,"src","pages","main.css"),
        index: path.resolve(__dirname, "src", "index.js"),//index 为key 即对应下文提到的chunk 
        main: path.resolve(__dirname, "src", "pages", "main.js"),
    }, 
    //指定编译的输出位置
    output: {
        path: path.resolve(__dirname, "public"), // 输出的路径
        filename: "[name]-wrap.js", // 打包后文件,这里的【name】对应入口的entry 的key
    },
    //定义各种资源文件的转换规则
    module: {
        rules: [
            {
                test: /.css$/i,
                use: [MiniCssExtractPlugin.loader, "css-loader"],
            },
            {
                test: /.png$/i,
                //,'file-loader' 有文件路径的
                use: {
                    loader: 'url-loader',
                    options: {
                        limit: 10 * 1024,
                        name: '[name]-[hash].png'
                    }
                }
            },
            {
                test: /.(js|jsx)$/,
                loader: "babel-loader",//babel 即将es6语法转换成es5语法的转换器
                exclude: /node_modules/,//node modules 文件夹下的要排除
            },
            {
                test: /.html$/,
                use: [
                    'html-withimg-loader'//提供html功能
                ]
            },
            // {
            //     test: /.css$/,
            //     use: [
            //         'style-loader',//后执行
            //         'css-loader',//先执行
            //     ]
            // }
        ],
    },

    plugins: [
        // 提取css成单独文件
        new MiniCssExtractPlugin({
            // 定义输出文件名和目录
            filename: "css/[name]-wrap-[hash].css",
        }),
        //生成html
        new HtmlWebpackPlugin({
            template: "./src/index.html",
            title: "react html",
            filename: "index.html",
            chunks: ["index"]//默认是所有chunks
        }),
        new HtmlWebpackPlugin({
            title: "webpack merge html",
            template: "./src/pages/index.html",//结合 html-withimg-loader 生成html
            filename: "./marge.html",
            chunks: ["index", "main"]//chunk 即上边entry的key
        }),
        new CleanWebpackPlugin({
            path: path.join(__dirname, "public"),
        }),
    ],
    devServer: {
        historyApiFallback: true,
        static: {
            directory: path.join(__dirname, "src"),
        },
        compress: true,
        port: 8000, //3000,3001,3002开头则会报错//
        //1.localhost:3000?q=345678清除缓存后可以访问
        //2. 换其他端口也可访问
        open: true,
    },
};

package.js 配置文件

{
  "name": "react-web",
  "private": true,
  "scripts": {
    "ssr": "node ./src/ssr",
    "dev": "webpack-dev-server --mode development",
    "build": "webpack --mode production" //编译过程
  },
  ···
 } 

执行结果

npm run build