About webpack externals

new

由于一些原因需要使用webpack交叉编译一个ts + yarn workspace的 app。web 端没有什么遇到什么问题,但是交叉编译服务器端一直报错,查阅了很多资料终于解决。

项目构成

.
├── package.json
├── packages
│   ├── script
│   │   ├── package.json
│   │   ├── server.ts
│   │   ├── tsconfig.webpack.json
│   │   └── webpack.server.ts
│   └── server
│       ├── index.ts
│       └── package.json
├── tsconfig.json
└── yarn.lock

3 directories, 9 files

简单的 server.ts

import express from "express"

const startServer = () => {
  const app = express()

  app.get("/", (_, res) => {
    res.send("ok")
  })

  app.listen(3000, () => {
    console.log("listening on 3000")
  })
}

export default startServer

最开始的 webpack.server.ts长这个样子。没什么特别的

import path from "path"
import webpack, { Configuration } from "webpack"
const serverSrc = path.resolve(__dirname, "./server.ts")
const distRoot = path.resolve(__dirname, "./dist")

const serverConfig: Configuration = {
  mode: "production",
  target: "node",
  node: {
    __dirname: false,
    __filename: false,
  },
  entry: {
    server: serverSrc,
  },
  output: {
    path: distRoot,
    filename: "server.js",
  },
  resolve: { extensions: [".js", ".ts"] },
  plugins: [
    new webpack.DefinePlugin({
      "process.env": JSON.stringify(process.env),
    }),
    new webpack.ProgressPlugin(),
  ],
  module: {
    rules: [
      {
        test: /\.ts$/,
        loader: [
          {
            loader: "ts-loader",
            options: {
              transpileOnly: true,
            },
          },
        ],
      },
    ],
  },
}

export default serverConfig

运行得到以下错误

WARNING in /home/yue/projects/webpack-express-ts-monorepo/node_modules/express/lib/view.js 81:13-25
Critical dependency: the request of a dependency is an expression
 @ /home/yue/projects/webpack-express-ts-monorepo/node_modules/express/lib/application.js
 @ /home/yue/projects/webpack-express-ts-monorepo/node_modules/express/lib/express.js
 @ /home/yue/projects/webpack-express-ts-monorepo/node_modules/express/index.js
 @ /home/yue/projects/webpack-express-ts-monorepo/node_modules/@package/server/index.ts
 @ ./server.ts

ERROR in server.js from Terser
Unexpected token: punc (:) [server.js:8974,12]
error Command failed with exit code 2.

经过查询,最常见的方法是使用 webpack-node-externals这个库将部分不适合 bundle 的库除外。于是进行修改

+  externals: [nodeExternals()],

再度编译还是得到相同错误。

ERROR in server.js from Terser
Unexpected token: punc (:) [server.js:8974,12]

再度查阅资料在这个问题里有提到yarn workspace需要做不同的处理,遇上按照这个回答再度修改,将根目录的node_modules也加入 exclude 对象。

-  externals: [nodeExternals()],
+  externals: [
+    nodeExternals(),
+    nodeExternals({
+      modulesDir: path.resolve(__dirname, '../../node_modules'),
+    }),
+  ],

yarn build 得到以下结果

yarn run v1.22.4
$ yarn workspace @package/script build
warning package.json: No license field
$ TS_NODE_PROJECT=./tsconfig.webpack.json webpack --config webpack.server.ts
Hash: 571930a4d43752f5eb7a
Version: webpack 4.42.1
Time: 163ms
Built at: 04/01/2020 11:47:55 PM
    Asset      Size  Chunks             Chunk Names
server.js  1.12 KiB       0  [emitted]  server
Entrypoint server = server.js
[0] ./server.ts 296 bytes {0} [built]
[1] external "@package/server" 42 bytes {0} [built]
Done in 3.23s.

交叉编译成功了诶!可以洗洗睡了!睡前yarn start运行一下交叉编译成果吧。

yarn run v1.22.4
$ yarn workspace @package/script start
warning package.json: No license field
$ node ./dist/server.js
/home/yue/projects/webpack-express-ts-monorepo/packages/server/index.ts:1
import express from "express";
^^^^^^

SyntaxError: Cannot use import statement outside a module
    at wrapSafe (internal/modules/cjs/loader.js:1063:16)
    at Module._compile (internal/modules/cjs/loader.js:1111:27)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1167:10)
    at Module.load (internal/modules/cjs/loader.js:996:32)
    at Function.Module._load (internal/modules/cjs/loader.js:896:14)
    at Module.require (internal/modules/cjs/loader.js:1036:19)
    at require (internal/modules/cjs/helpers.js:72:18)
    at Object.<anonymous> (/home/yue/projects/webpack-express-ts-monorepo/packages/script/dist/server.js:1:1118)
    at r (/home/yue/projects/webpack-express-ts-monorepo/packages/script/dist/server.js:1:110)
    at Object.<anonymous> (/home/yue/projects/webpack-express-ts-monorepo/packages/script/dist/server.js:1:1077)
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
error Command failed.
Exit code: 1
Command: /usr/bin/node
Arguments: /usr/lib/node_modules/yarn/lib/cli.js start
Directory: /home/yue/projects/webpack-express-ts-monorepo/packages/script
Output:

info Visit https://yarnpkg.com/en/docs/cli/workspace for documentation about this command.
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

什么,packages/server根本没有被交叉编译!!??

看一眼交叉编译出来的文件:

!function(e){var t={};function r(n){if(t[n])return t[n].exports;var o=t[n]={i:n,l:!1,exports:{}};return e[n].call(o.exports,o,o.exports,r),o.l=!0,o.exports}r.m=e,r.c=t,r.d=function(e,t,n){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)r.d(n,o,function(t){return e[t]}.bind(null,o));return n},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="",r(r.s=0)}([function(e,t,r){"use strict";var n=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0}),n(r(1)).default()},function(e,t){e.exports=require("@package/server")}]);

require("@package/server")是个什么鬼嘛 (╯°□°)╯︵ ┻━┻

再度回到刚才那个 stackoverflow 的问题。仔细查看之后得出以下结论

  • 我们需要交叉编译所有自己写的 ts 文件
  • 我们没理由交叉编译所有第三方库

于是查阅 webpack 的官方文档再再次出做修改

  externals: [
    (_, req, cb) => {
      if (!/^@package/.test(req) && req[0] !== "." && req[0] !== "/") {
        return cb(null, `commonjs ${req}`);
      }
      cb(null, undefined);
    }
  ],

当一个 import path 是本地的 package(@package开头),相对路径("."开头), 绝对路径("/"开头)时进行交叉编译,不是的时候直接使用原来的commonjs模块。

再度编译结果正常。

yarn run v1.22.4
$ yarn workspace @package/script build
warning package.json: No license field
$ TS_NODE_PROJECT=./tsconfig.webpack.json webpack --config webpack.server.ts
Hash: f0adf7b87e9622543054
Version: webpack 4.42.1
Time: 176ms
Built at: 04/02/2020 12:02:19 AM
    Asset      Size  Chunks             Chunk Names
server.js  1.42 KiB       0  [emitted]  server
Entrypoint server = server.js
[0] ./server.ts 296 bytes {0} [built]
[2] external "express" 42 bytes {0} [built]
    + 1 hidden module
Done in 3.22s.

尝试运行yarn start

yarn run v1.22.4
$ yarn workspace @package/script start
warning package.json: No license field
$ node ./dist/server.js
listening on 3000

到此为止解决了问题 💞