webpack5 构建优化方案看这篇就够了!【Node.js进阶】

15 篇文章 2 订阅
8 篇文章 0 订阅

无论在面试还是内部晋升,webpack 构建优化方案 一直都是非常重要的部分。


一、项目完成目标

  • 支持 es6+语法
  • 开发热更新
  • webpack5 构建
  • 接口搭建
  • 路由合并,路由自动注册
  • 添加项目规范
  • 配置自定义别名

二、搭建项目

# 创建项目目录
mkdir webpack5-node

# 进入webpack5-node文件夹
cd webpack5-node

# 初始化package.json
npm init -y

# 创建源码目录
mkdir src
1. 安装koa、@koa/router (如果已经配置可路过)
yarn add koa @koa/router
2. 创建入口文件
touch src/app.js
3. 安装构建依赖
yarn add -D webpack webpack-cli @babel/node @babel/core @babel/preset-env babel-loader clean-webpack-plugin nodemon webpack-node-externals webpack-merge rimraf
4. 在项目根目录添加 .babelrc 文件
// .babelrc
{
  "presets": [
    [
      "@babel/preset-env",
      {
        "targets": {
          "node": "current"
        }
      }
    ]
  ]
}
5. 添加测试接口

app.js 中添加测试接口,由于已经配置了 babel 解析,所以可以直接在 app.js 中写 es6+语法

// app.js
import Koa from 'koa'
import Router from '@koa/router'

const app = new Koa()
const router = new Router()

router.get('/', async ctx => {
  ctx.body = {
    status: 200,
    message: 'success',
    data: {
      nickname: 'Simon',
      title: '前端工程师',
      content: 'webpack5构建node应用'
    }
  }
})

app.use(router.routes()).use(router.allowedMethods())

const port = 3000
app.listen(port, () => console.log(`服务启动在 ${port} 端口`))
6. 启动服务
npx babel-node src/app.js
7. 请求接口进行测试

todo

三、配置 webpack

中文文档

1. 核心概念
  • entry:入口;指示 webpack 应该使用哪个模块,默认值是 ./src/index.js
  • output:输出;output 属性告诉 webpack 在哪里输出它所创建的 bundle,默认值是 ./dist/main.js
  • loaderloader 负责完成项目中各种各样资源模块的加载;
  • plugins:插件;用来解决项目中除了资源模块打包以外的其他自动化工作。包括:打包优化,资源管理,注入环境变量;
  • mode:模式;通过选择 development, productionnone 之中的一个,来设置 mode 参数,你可以启用 webpack 内置在相应环境下的优化。其默认值为 production
2. 在项目根目录创建 webpack.config.js 文件
// webpack.config.js
const { DefinePlugin } = require('webpack')
const nodeExternals = require('webpack-node-externals')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')

module.exports = {
  // 打包编译为某一端侧的可使用代码  默认值:web  https://webpack.docschina.org/configuration/target/
  target: 'node',

  // 打包模式,可选择值:development、production
  mode: 'development',

  // 控制是否生成,以及如何生成 source map。 https://webpack.docschina.org/configuration/devtool/#root
  devtool: 'eval-cheap-source-map',

  // 打包模块入口文件
  entry: {
    server: `${process.cwd()}/src/app.js`
  },

  // 打包后的输入文件
  output: {
    filename: '[name].bundle.js',
    path: `${process.cwd()}/dist`
  },

  // 匹配解析规则
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        use: {
          loader: 'babel-loader'
        },
        exclude: [`${process.cwd()}/node_modules`]
      }
    ]
  },

  // 构建过程中使用的插件
  plugins: [
    new CleanWebpackPlugin(),
    new DefinePlugin({
      'process.env': {
        NODE_ENV: JSON.stringify(
          process.env.NODE_ENV === 'production' ||
            process.env.NODE_ENV === 'prod'
            ? 'production'
            : 'development'
        )
      }
    })
  ],

  // 防止第三方依赖被打包
  externals: [nodeExternals()]
}
3. 测试构建
npx webpack

在这里插入图片描述

构建成功!

🤔❓
在实际开发中可能会存在开发环境和生产环境的构建,所以单凭一个配置还不能达到实际的需求,接下来对开发环境和生产环境分别配置。

在项目根目录创建 config 文件,并创建三个文件分别是:

  • webpack.config.base.js 文件存放开发环境和生产环境都是需要的构建配置
  • webpack.config.dev.js 文件存放开发环境的构建配置
  • webpack.config.prod.js 存放生产环境的构建配置
4. 优化构建配置
  • mode 独立于构建环境,开发环境为(development)、生产环境为(production)
  • devtool 只有在开发环境下才会存在
  • stats 属性让你更精确地控制打包后的信息该怎么显示 【Stats 对象

Tips 🤔
由于每个开发环境和生产环境都是独立的构建配置,所以要在构建时要合并基础配置;
安装 webpack-merge 合并构建配置
npm i -D webpack-merge

  • 优化基础构建配置 webpack.config.base.js

    // config/webpack.config.base.js
    const { DefinePlugin } = require('webpack')
    const nodeExternals = require('webpack-node-externals')
    const { CleanWebpackPlugin } = require('clean-webpack-plugin')
    
    module.exports = {
      // 打包编译为某一端侧的可使用代码  默认值:web  https://webpack.docschina.org/configuration/target/
      target: 'node',
    
      // 打包模式,可选择值:development、production
      // mode: "development",
    
      // 控制是否生成,以及如何生成 source map。 https://webpack.docschina.org/configuration/devtool/#root
      // devtool: "eval-cheap-source-map",
    
      // 打包模块入口文件
      entry: {
        server: `${process.cwd()}/src/app.js`
      },
    
      // 打包后的输入文件
      output: {
        filename: '[name].bundle.js',
        path: `${process.cwd()}/dist`
      },
    
      // 匹配解析规则
      module: {
        rules: [
          {
            test: /\.(js|jsx)$/,
            use: {
              loader: 'babel-loader'
            },
            exclude: [`${process.cwd()}/node_modules`]
          }
        ]
      },
    
      // 构建过程中使用的插件
      plugins: [
        new CleanWebpackPlugin(),
        new DefinePlugin({
          'process.env': {
            // 设置环境变量 NODE_ENV
            NODE_ENV: JSON.stringify(
              process.env.NODE_ENV === 'production' ||
                process.env.NODE_ENV === 'prod'
                ? 'production'
                : 'development'
            )
          }
        })
      ],
    
      // 防止第三方依赖被打包
      externals: [nodeExternals()]
    }
    
  • 开发环境的构建配置 webpack.config.dev.js

    // config/webpack.config.dev.js
    const { merge } = require('webpack-merge')
    const baseWebpackConfig = require('./webpack.config.base')
    
    const webpackConfig = merge(baseWebpackConfig, {
      devtool: 'eval-cheap-source-map',
      mode: 'development',
    
      // 是否添加关于子模块的信息。
      stats: { children: false }
    })
    
    module.exports = webpackConfig
    
  • 生产环境的构建配置 webpack.config.prod.js

    生产环境构建时要进行代码压缩,需要安装 terser-webpack-plugin
    命令:npm i -D terser-webpack-plugin

    // config/webpack.config.prod.js
    const { merge } = require('webpack-merge')
    const TersetWebpackPlugin = require('terser-webpack-plugin')
    const baseWebpackConfig = require('./webpack.config.base')
    
    const webpackConfig = merge(baseWebpackConfig, {
      devtool: 'eval-cheap-source-map',
      mode: 'production',
      stats: { children: false },
    
      // 优化配置
      optimization: {
        // 压缩配置
        minimize: true,
        minimizer: [new TersetWebpackPlugin()],
    
        // 分块策略
        splitChunks: {
          // 缓存组 https://webpack.docschina.org/plugins/split-chunks-plugin/#splitchunkscachegroups
          cacheGroups: {
            commens: {
              name: 'commons',
              chunks: 'initial',
              minChunks: 3,
              enforce: true
            }
          }
        }
      }
    })
    
    module.exports = webpackConfig
    
  • 添加构建脚本命令

    • 设置环境变量 NODE_ENV,由于各环境配置的差异问题,cross-env 可以有效的解决跨平台设置环境变量的问题;

    • 它是运行跨平台设置和使用环境变量(Node 中的环境变量)的脚本。

    • 安装命令:npm i -D cross-env 安装成功后配置构建命令

    • package.jsonscripts 中添加如下命令:

      "build": "cross-env NODE_ENV=prod webpack --config config/webpack.config.prod.js",
      "dev": "cross-env NODE_ENV=dev nodemon --exec babel-node --inspect src/app.js",
      
  • 启动开发环境服务

    npm run dev
    
    • 运行之后的效果图如下todo
  • 启动编译构建命令

    npm run build
    
    • 运行效果如下图:
      在这里插入图片描述
    • 查看 dist 文件夹下被编译后的文件:
      在这里插入图片描述

    代码被压缩成了一整行!

四、路由自动注册

1. 使用 require-directory

src 文件夹下新建 routesapi 两文件夹;
routes 是集成当前项目的所有路由
api 文件是存放项目的所有接口文件

  • 安装 require-directory,这个包的作用可以将一个目录下的所有模块来自动注册Koa应用的路由,从而避免手动导入和注册每个路由文件。

    npm i require-dirctory
    
  • 创建 src/api/v1 下创建 demo.jstest.js 文件

    // src/api/v1/demo.js
    import Router from '@koa/router'
    
    const router = new Router({ prefix: '/api/v1' })
    
    router.get('/demo', async ctx => {
      ctx.body = {
        status: 200,
        message: 'message',
        data: {
          file: 'demo.js',
          title: 'webpack 5 构建node应用',
          content: 'koa + @koa/router + require-dirctory'
        }
      }
    })
    
    export default router
    
    //  src/api/v1/test.js
    import Router from '@koa/router'
    
    const router = new Router({ prefix: '/api/v1' })
    
    router.get('/test', async ctx => {
      ctx.body = {
        status: 200,
        message: 'message',
        data: {
          file: 'test.js',
          title: 'webpack 5 构建node应用',
          content: 'koa + @koa/router + require-dirctory'
        }
      }
    })
    
    export default router
    
  • 配置 src/routes/index.js

    // src/routes/index.js
    import Router from '@koa/router'
    import requireDirectory from 'require-directory'
    
    // 接口存放目录路径
    const apiDirectory = `${process.cwd()}/src/api`
    
    function initLoadRoutes(app) {
      requireDirectory(module, apiDirectory, {
        visit({ default: router }) {
          if (router instanceof Router) {
            app.use(router.routes())
          }
        }
      })
    }
    
    export default initLoadRoutes
    
  • 修改 src/app.js 文件

    // src/app.js
    import Koa from 'koa'
    import initLoadRoutes from './routes/index'
    
    const app = new Koa()
    
    // 在入口文件中执行
    initLoadRoutes(app)
    
    const port = 3002
    app.listen(port, () => console.log(`服务启动在${port}端口`))
    
  • 测试请求

🎉 至此,自动注册路由就大功告成了,后面我们定义接口的时候就用手动一个一个的引入,只管往 api 文件夹里写接口就好了。

2. 使用 require.context(webpack)功能

require 依赖管理

eg.:

.
├── modules
│   ├── adminRouter.js
│   ├── commentsRouter.js
│   ├── contentRouter.js
│   ├── loginRouter.js
│   ├── publicRouter.js
│   ├── userRouter.js
│   └── wxRouter.js
└── routes.js

目标:使用 routes.js 来动态加载 modules 目录中的 .js 的路由文件,其他的比如:vuexvue-router 等场景,都适合。

上代码

// routes.js

import combineRoutes from 'koa-combine-routers'

// 加载目录中的Router中间件
const moduleFiles = require.context('./modules', true, /\.js$/)

// reduce方法去拼接 koa-combine-router所需的数据结构 Object[]
const modules = moduleFiles.keys().reduce((items, path) => {
  const value = moduleFiles(path)
  items.push(value.default)
  return items
}, [])

export default combineRoutes(modules)

使用方法

index.js 入口文件中:

import router from './routes/routes'

app.use(router())

🧀🧀 两个小知识:

  1. 使用 koa-combine-routers 可以合并多个路由
  2. 使用 require.context 可以动态引入多个文件

五、配置别名

在日常开发中我们引入一些封装好的方法或者模块总是写很长很长的文件路径;比如:
require('../../../../some/very/deep/module')
import format from '../../../../utils/format'

为了告别这种又臭又长的路径我们就可以使用一些解放生产力的方法了(哈哈哈哈,不会偷懒的程序员不是好程序员 🤭)

配置别名有两种方式,一种是 webpack,另一种是通过 module-alias

1. 使用 webpack 的别名功能

官方文档: resolve.alias

配置方式,非常的简单方便:

const path = require('path');

module.exports = {
  //...
  resolve: {
    alias: {
      '@': path.resolve(__dirname, 'src/'),
      // ...
    },
  },
};
2. 使用 module-alias
  • 安装依赖

    npm i module-alias
    
  • package.json 中添加自定义别名

    "_moduleAliases": {
        "@": "./src",
        "@controller": "./src/controller"
    }
    
  • 在入口文件的顶部引入 module-alias/register,也就是在 app.js 的顶部引入

    // src/app.js
    require('module-alias/register')
    ...
    
  • 配置成功后,将 /src/api/v1 内的逻辑全部提到 src/controller 中,使用别名引入 controller 中文件,修改后如下:

    // src/api/v1/demo.js
    import Router from '@koa/router'
    import DemoController from '@controller/demo/'
    
    const router = new Router({ prefix: '/api/v1' })
    
    router.get('/demo', DemoController.demo)
    
    export default router
    
    // src/api/v1/test.js
    import Router from '@koa/router'
    import TestController from '@controller/test'
    
    const router = new Router({ prefix: '/api/v1' })
    
    router.get('/test', TestController.test)
    
    export default router
    
    // src/controller/v1/demo.js
    class DemoController {
      constructor() {}
    
      async demo(ctx) {
        ctx.body = {
          status: 200,
          message: 'message',
          data: {
            file: 'test.js',
            title: 'webpack 5 构建node应用',
            content: 'koa + @koa/router + require-dirctory'
          }
        }
      }
    }
    
    export default new DemoController()
    
    // src/controller/v1/test.js
    class TestController {
      constructor() {}
    
      async test(ctx) {
        ctx.body = {
          status: 200,
          message: 'message',
          data: {
            file: 'test.js',
            title: 'webpack 5 构建node应用',
            content: 'koa + @koa/router + require-dirctory'
          }
        }
      }
    }
    
    export default new TestController()
    
  • 测试接口


🙋 🙋
如果 git commit 时 lint-staged 没有通过:
在这里插入图片描述

上述问题是因为 eslint 发现 @controller/* 开头的在 node_modules 中没有找到,所以配置 eslint 就好了:

// src/eslintrc.js
module.exports = {
  //...
  rules: {
    'import/no-unresolved': [2, { ignore: ['^@/', '@controller'] }] // @和@controller 是设置的路径别名
  }
}

在这里插入图片描述

这个问题是由于 constructor 构造函数为空引起的,在 eslintrc.js 添加配置即可:
'no-empty-function': ['error', { allow: ['constructors'] }]


希望上面的内容对你的工作学习有所帮助!欢迎各位一键三连哦~

各位 加油!

原创不易,还希望各位大佬支持一下 \textcolor{blue}{原创不易,还希望各位大佬支持一下} 原创不易,还希望各位大佬支持一下

👍 点赞,你的认可是我创作的动力! \textcolor{green}{点赞,你的认可是我创作的动力!} 点赞,你的认可是我创作的动力!

⭐️ 收藏,你的青睐是我努力的方向! \textcolor{green}{收藏,你的青睐是我努力的方向!} 收藏,你的青睐是我努力的方向!

✏️ 评论,你的意见是我进步的财富! \textcolor{green}{评论,你的意见是我进步的财富!} 评论,你的意见是我进步的财富!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值