使用 Webpack

本章对应视频讲解如下

wp开发环境设置

wp生产环境设置

边看视频讲解,边学习教程

前端工程化

参考

https://segmentfault.com/a/1190000040246060

https://juejin.cn/post/6917447582985748493

早期的前端开发就是实现web页面,主要是开发 HTML、CSS 文件 和一些简单的js代码

后来前端技术高速发展更新,往往前端项目的功能越来越多,开发工作量越来越大。

这时,前端工程化的概念被提出了,主要解决代码冗余,项目可维护性,提升版本迭代速度等等一系列的问题。

总之是要 提高 开发效率,减少维护成本。


前端工程化要做的事情 包括: 团队代码编写规范、开发流程规范(包括需求、开发、测试、发布等)、配置管理规范、项目工具规范 等等


我们这里介绍一下 前端工程化 常用的 项目构建工具 webpack

为什么需要构建工具

以我们项目实战为例,到 模块化结束 这个阶段, 我们发现面临如下问题:


上面的问题,通过前端项目构建工具,可以比较好的解决

我们这里介绍一下 前端工程化 常用的 项目构建工具 webpack

webpack 简介

webpack 是构建工具,功能通常包括 编译打包

浏览器能直接运行的 只有 HTML、CSS、JS 3种类型的语言内容

但是我们的项目往往包含其它类型的代码内容,所以 需要先 编译 为浏览器能直接运行的 HTML、CSS、JS

这些内容包括:


项目构建往往还包含打包,主要包括:


所以 webpack最终产生的js文件,通常是多个源文件经过处理过的最终合并结果


注意,webpack 构建的依赖关系是根据模块的导入关系决定的,

所以,项目代码需要先模块化好,再使用 webpack


webpack功能非常丰富,我们这里只涉及到最常用的一部分

安装

安装 webpack工具库

首先当然要安装 webpack 工具库,执行如下命令

npm install webpack webpack-cli  --save-dev

安装 JSX编译库

然后,安装 webpack 构建过程中需要的其它工具库

我们这里的示例 主要是要转化JSX, 当然要安装 转化工具, 这里我们选择 Babel这个工具

执行如下命令

npm install --save-dev @babel/core @babel/cli @babel/preset-react

要让 Babel 有效集成到 webpack 的构建过程,还需要安装 babel-loader

执行如下命令

npm install --save-dev babel-loader  

也可以选择esbuild,

// 确保先安装 esbuild 
npm install --save-dev esbuild

// 再安装  针对webpack 的esbuild loader库
npm install --save-dev esbuild-loader  



开发环境

运行webpack时,它根据配置文件执行不同的功能

在开发调试时 运行 webpack, 是为了 调试、测试 我们的前端代码,

所以需要webpack 提供本地的web服务器,并且为调试程序提供便利

安装 webpack-dev-server

webpack-dev-server 是一个开发环境下的 web服务,

作用类似 vscode live server 扩展


执行如下命令 安装

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

配置文件

先做开发调试环境的 webpack 配置文件。

配置文件就是一个js文件,放在项目根目录下,

文件名可以自己定义,比如 wpcfg.run_dev_server.js

示例配置如下

视频讲解里面的 12:50 处对proxy配置的讲解是老版本的 现在版本的配置有了一些改变,以下面示例中的写法为准

var path = require('path');

module.exports = {
    // 指明是开发模式
    mode : 'development',

    // 入口模块定义
    entry: {
        hyms: 'hyms.js',
    },
    
    module: {      
        // 模块处理规则
        rules: [
            {
                test: /\.jsx$/,
                loader: 'babel-loader',
                exclude: /node_modules/,
                options: {
                    presets: [ '@babel/preset-react']
                }
            },
        ]
    },

    // 模块搜索路径
    resolve: {
        modules: [
            path.resolve('./'),
            path.resolve('./node_modules')
        ],


    },

    // 一种包含源码的编译结果,方便调试对应到源代码
    devtool: 'inline-source-map',

    // web服务配置
    devServer: {

        //端口
        port: 80, 

        // 指定 非webpack产生的 其他文件的根目录,
        // 比如 html、一些公共的库、图片资源,
        static:{
            // __dirname 代表当前项目目录
            directory: __dirname, 
        } ,
        

        // 指定 本dev服务可以代理的请求的url 和 被代理的服务地址
        // 通常是代理 后端API服务的请求
        proxy: [
          {
            context: ['/api'],
            target: 'http://localhost:8234',
          },
        ],
    },


};

启动

有两种方式可以 启动 webpack开发服务


推荐使用方法2启动

启动服务后,修改任意一个jsx,发现更新可以立即重新编译加载。


注意 : 运行webpack js程序的,是 node.js 而不是浏览器。

生产环境

我们开发好的项目代码,最终要部署到生产环境上

那么生产环境大体是怎样的呢?

这就必须先了解一下系统的大体架构


前后端分离的架构下,系统架构大体如下图所示

我们下面就是讲解其中前端部分的部署

Nginx 配置

首先看 web服务、反向代理 软件

生产环境是 不应该 使用 webpack dev server 作为 web 服务的

Nginx 是比较常用的 web服务/反向代理 软件


生产环境下,Nginx基本都是安装在Linux环境下,需要大家熟悉Linux

大家,

可以 点击这里,学习 Ubuntu Linux

可以 点击这里,学习 Nginx在Ubuntu上的安装


使用Nginx的关键是配置好配置文件

下面是一个 Nginx 配置文件的片段, 包含了通常要修改的部分

http {
    
    # 后端服务 (比如Gunicorn/Django)
    # 主要配置 名称(这里是 apiserver) 地址和端口  
    upstream apiserver  {    
        # 和后端服务 最多 20 个 空闲连接
        keepalive 20;        
        # 后端服务地址和端口
        server 127.0.0.1:8234; 
    }
    
    # 配置 HTTP 服务器信息
    server {
        # 网站的域名,这里请改为你申请的域名, 如果没有域名,使用IP地址。
        server_name  www.yoursite.com;

        # 网站的端口
        listen       80;

      
        # 配置动态数据请求怎么处理
        # 下面这个配置项说明了,当 HTTP 请求 URL以 /api/ 开头,
        # 则转发给 apiserver 后端服务去处理    
        location /api/ {
            proxy_pass         http://apiserver;
            proxy_redirect     off;
            proxy_set_header   Host $host;
            proxy_http_version 1.1;
            add_header Access-Control-Allow-Origin *;
        }

        # 访问静态文件的根目录,通常为html、css、js、图片资源等
        location / {
            root   websites/bysms;
        }
    }

所以,我们前端开发的结果文件,包括 js、css、图片资源文件 ,应该上传放在 location / -> root 指定的根目录下面

本例中,就是 websites/bysms 目录


那么我们应该产生怎样的前端结果文件,放到web服务目录下呢?


首先,所有的库文件(比如react),应该使用production版,而不是development版。

因为 production版 体积更小 并且 执行性能更高

webpack配置

另外我们自己的代码 可以用 webpack 构建出生产环境的代码文件


为了方便大家直观的查看构建结果,推荐安装 webpack-bundle-analyzer

执行 npm i webpack-bundle-analyzer --save-dev 命令安装即可


然后为生产环境代码构建创建一个webpack配置文件,文件名可以叫 wpcfg.build_prod.js

配置示例如下

var path = require('path');
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

module.exports = {
    // 生产环境模式
    mode : 'production',
    
    // 入口模块
    entry: {
        hyms:'hyms.js'
    },
    
    // 输出结果设置
    output: {
        // __dirname是node.js 的 内置变量, 代表当前目录
        path: 'd:/tools/nginx-1.20.2/websites/bysms/',
        filename: '[name].js'
    },
    
    // 优化设置
    optimization: {
        // 代码分割
         splitChunks: {
           chunks: 'all',
           automaticNameDelimiter: '-',
         },
    },
    
    module: {
        rules: [
            {
                test: /\.(js|jsx)$/,
                loader: 'babel-loader',
                exclude: /node_modules/,
                options: {
                    presets: [ '@babel/preset-react']
                }
            },
        ]
    },


    resolve: {
        modules: [
            path.resolve('./'),
            path.resolve('./node_modules')
        ],
    },

    plugins: [
        new BundleAnalyzerPlugin({analyzerMode:'static'})
    ]


};

执行构建

有两种方式可以 执行 webpack 构建任务


执行后,可以发现产生 一个结果文件hyms.js 。

打开 hyms.js 发现其内容还被最小化了。


通过 webpack-bundle-analyzer 产生的report页面显示: hyms里面包含了所有的其它import的依赖模块


大家就可以把这个结果文件和其它不需要webpack构建的文件,比如css、图片、库目录等,放到 web服务的静态文件目录下

静态导入和动态导入

静态导入

webpack 根据代码中的 import 分析 模块之间的依赖关系。

最简单的就是静态导入,比如

import {Medicine} from "./medicine.jsx"
import {Customer} from "./customer.jsx"
import Order  from "./order.jsx"

webpack 就会把这些模块处理好后和当前模块打包在一个Bundle中

动态导入-文件名确定

如果是 动态 import 比如

if(condition)
  const { default: App } = await import('./customer.js');

也能识别出来。

并且 动态 import 的 模块 不和 bundle 在一个chunk中。

动态导入-文件名为变量

但是如果import里面的是变量,比如

let hash = window.location.hash
const URL2MODULE = {
  ''         : './customer.js',
  'customer' : './customer.js',
  'medicine' : './medicine.js',
  'order'    : './order.js',
}

hash = hash.slice(1)
let modulePath = URL2MODULE[hash]
if (!modulePath)
  modulePath = URL2MODULE['']

// 导入的是变量
const { default: App } = await import(modulePath);

那就不行了。

因为 await import(modulePath)modulePath 是变量,

要运行时才能确定内容,webpack 没有办法知道导入的到底是什么。


那怎么办呢?

可以参考官方文档 https://webpack.js.org/api/module-methods/#dynamic-expressions-in-import


方法是:加上 路径前缀 限制搜索范围,而且要 有 神奇注释 指明,如下

  
  let hash = window.location.hash
  

  const URL2MODULE = {
    ''         : 'customer.jsx', // 去掉前缀./
    'customer' : 'customer.jsx', // 去掉前缀./ 
    'medicine' : 'medicine.jsx', // 去掉前缀./
    'order'    : 'order.jsx',    // 去掉前缀./
  }

  hash = hash.slice(1)
  let modulePath = URL2MODULE[hash]
  if (!modulePath)
    modulePath = URL2MODULE['']

  // let App = modulePath
  const { default: App } = await import(
    /* webpackInclude: /.*(customer|medicine|order)\.jsx/ */
    /* webpackExclude: /.*pracs.+/ */
    `./${modulePath}`);  // 加上前缀./ , 这样限制搜索范围,否则webpack放弃搜索


要特别注意: webpackIncludewebpackExclude 神奇注释 里面正则表达式匹配的是 整个路径, 而不是 只有变量那部分。


运行发现产生的动态加载的 bundles名称类似: 93.js、600.js、397.js


那程序运行时,await import('./customer.jsx') 是怎么找到对应的js文件和代码的呢?

原来,这种情况,webpack会修改入口bundle代码 (可以打开hyms.js看看),

会产生一些额外的辅助代码,指定原来的动态导入文件 和 新bundle文件之间的对应关系,

如下所示

n={
  "./customer.jsx":[93,93],
  "./medicine.jsx":[600,600],
  "./order.jsx":[397,397]
}

当程序动态加载 ./customer.jsx 时, 后面代码会查询到对应的其实是 93.js ,转化为实际导入 93.js


其它

去除重复包含模块 - 暂时解决不了以后再说

通过webpack-bundle-analyzer 发现,每个

93.js、600.js、397.js 都包含了同一个文件 crud.js

package.json添加命令

package.json中添加 run-dev-server 和 build-product

  "scripts": {
    "run-dev-server": "webpack serve --config wpcfg.run_dev_server.js --progress",
    "build-product": "webpack --config wpcfg.build_prod.js"
  },

使用 esbuild-loader

module: {
        rules: [
            {
                test: /\.(js|jsx)$/,
                loader: 'esbuild-loader',
                exclude: /node_modules/,
                options: {
                    loader:'jsx',
                    target: 'es2015'
                }
            },
        ]