webpack打包koa项目配置

112 阅读4分钟

先写一个简单的demo

const Koa = require('koa');
const app = new Koa();
const Router = require('@koa/router');
const router = new Router();
router.get('/', (ctx) => {
  ctx.body = 'ok';
  console.log('get');
});
const port = process.env.PORT || 4000;
app.use(router.routes()).use(router.allowedMethods);
app.listen(port, () => {
  console.log('监听4000端口');
});

启动项目,可以执行node src/app.js,打印出 '监听4000端口'。但其实我们在写代码的时候希望代码能够重新启动并编译,开发环境我们用nodemon, 执行nodemon src/app.js,修改代码之后可以看到重新启动了。

不过我们通常会比较熟悉esm的模块规范,所以将上面的代码的require改为熟悉的import语法

import Koa from 'koa';
const app = new Koa();
import Router from '@koa/router';
const router = new Router();
router.get('/', (ctx) => {
  ctx.body = 'ok';
  console.log('get');
});
const port = process.env.PORT || 4000;
app.use(router.routes()).use(router.allowedMethods);
app.listen(port, () => {
  console.log('监听4000端口');
});

这时如果直接执行nodemon src/app.js会报错,Cannot use import statement outside a module,因为node默认是commonjs规范,几种解决办法:

  1. package.json中的type设置为module, 这样NodeJs会认为所有的文件都是esm模块了,不过其他的一些设置文件我们是没必要去写成import语法的,commonJs规范在构建处理文件时更高效
  2. .js后缀改为.mjs,nodeJs就知道要以esm规范去处理了
  3. 使用babel-node对代码进行实时转译(推荐)

这里要用到babel-node的原因是,我们写了es6+的语法,但是nodeJs默认是cmj规范,所以我们要对语法以及模块规范进行转换,这时就要用到babel了,babel会将es6+语法转为目标环境支持的语法(浏览器/node)。babel是基于插件和预设工作的,我们先引入@babel/preset-env包,设置target为node

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "targets": {
          "node": "current"
        }
      }
    ]
  ]
}

意思是基于这些预设,帮我转换为node中的模块规范。

执行npx babel-node src/app.js, 正常打印,但是可能大家会发现,在执行完命令终端那里会闪一下,因为babel-node是即时编译的,性能较差,所以我们也只是在开发环境用,不会在生产环境用。在生产环境打包编译好,再部署。同样的,继续用nodemon来监听文件变化,在package.json中我们这样配置:npx nodemon --exec babel-node src/app.js,意思是 通过babel-node运行入口文件,并监听文件变化。

以上是本地启动项目,对项目构建时,我们要区分一下环境,开发环境和生产环境,它们还有一些公用的配置,所以新建一个config文件夹,一共有三个文件,webpack.config.dev.js,webpack.config.prod.js,webpack.config.base.js, 配置分别如下:

// webpack.config.base.js
const path = require('path');
const webpack = require('webpack');
const nodeExternals = require('webpack-node-externals');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const Dotenv = require('dotenv-webpack');
const dotenv = require('dotenv');
// 加载 .env 文件
dotenv.config();
const webpackConfig = {
  // 这里是告诉webpack目标环境是NodeJs, 主要在于模块解析规则的不同。比如不加的话,模块解析是esm规范,如果是node, 解析是commonjs规范。对于nodeJs而言,解析到require('path')的时候,webpack知道这是一个nodejs内置模块,否则会报错,因为在整个项目中都找不到path.
  target: 'node',
  entry: {
    // 设置入口文件
    server: path.join(__dirname, '../src/app.js'),
  },
  output: {
    // 设置打包后的文件和位置
    filename: '[name].bundle.js',
    path: path.join(__dirname, '../dist'),
  },
  module: {
    rules: [
      {
        test: /\.js|jsx$/,
        // 这里可以用swc-loader代替babel-loader加快打包速度
        use: {
          loader: 'swc-loader',
        },
        // 尽量将 loader 应用于最少数量的必要模块,因此设置include
        // 只针对该目录下的js文件进行处理
        // include: path.join(__dirname, '../src'),
      },
    ],
  },
  resolve: {
    // 模块解析目录,默认是node_modules,
    modules: ['node_modules', path.resolve('src')],
    // 在导入的时候通常是不加文件后缀的,在打包的时候webpack自动添加这些后缀进行匹配
    extensions: ['.js', '.json'],
    alias: {
      // 设置别名指向对应目录
      '@': path.join(__dirname, '../src'),
    },
  },
  externals: [nodeExternals()], // 排除对node_modules里的依赖进行打包
  plugins: [
    new Dotenv({
      // 根据不同的环境加载环境变量
      path: path.join(__dirname, `../.env.${process.env.NODE_ENV}`),
    }),
    new CleanWebpackPlugin(), // 打包前清除dist目录
  ],
};

module.exports = webpackConfig;
// .swcrc
{
  "module": {
    "type": "es6",
    "ignoreDynamic": true
  },
  "jsc": {
    "parser": {
      "syntax": "typescript",
      "dynamicImport": true,
      "decorators": true,
      "tsx": true
    },
    "transform": {
      "legacyDecorator": true,
      "decoratorMetadata": true,
      "react": {
        "runtime": "automatic",
        "throwIfNamespace": true,
        "useBuiltins": true
      }
    },
    "target": "esnext",
    "loose": true,
    "externalHelpers": true,
    "keepClassNames": false
  }
}

// webpack开发配置

const { merge } = require('webpack-merge');
const baseConfig = require('./webpack.config.base');

// 通过webpack-merge合并基础配置,添加开发时配置
const webpackConfig = merge(baseConfig, {
  mode: 'development', // 开发模式
  devtool: 'eval-source-map', // 开发时出错能知道在源代码中哪一行
  stats: {
    children: false, // webpack打包时子模块信息设置不显示
    modules: false, // 不显示模块信息
  },
});

module.exports = webpackConfig;
// webpack.config.prod.js
const { merge } = require('webpack-merge');
const baseConfig = require('./webpack.config.base');
const TerserPlugin = require('terser-webpack-plugin');

// 通过webpack-merge合并基础配置,添加生产时配置
const webpackConfig = merge(baseConfig, {
  mode: 'production', // 生产模式下,webpack自己也有开启一些优化,开发模式同理
  // 输出的信息的内容显示
  stats: {
    children: false, // webpack打包时子模块信息设置不显示
    warnings: false, // 警告不显示
  },
  optimization: {
    minimizer: [
      // terser-webpack-plugin插件可以压缩js代码
      new TerserPlugin(),
    ],
  },
});

module.exports = webpackConfig;

补充说一下Dotenv-webpack踩的坑,用的时候忘看官网了

As such, it does a text replace in the resulting bundle for any instances of 'process.env'

它是会替换打包好的文件中的process.env为.env文件中的变量,直接访问是访问不到的。如果要想打印的话,可以用Dotenv.

const dotenv = require('dotenv');
// 加载 .env 文件
dotenv.config();

这样就能打印了。

以上webpack webpack-cli最新版本可用

参考资料:www.ljclucky.com/2021/webpac…