Skip to content
This repository was archived by the owner on Nov 13, 2021. It is now read-only.

Commit 220700e

Browse files
committed
feat(webpack5): move to webpack 5 + bunch of fixes
- enforce using local (local to aws-lambda-nodejs-webpack) webpack/webpack-cli binaries - enforce using our local webpack & babel plugins/loaders - upgrade all deps - use webpack 5 - use webpack caching as much as possible - use production mode of webpack - separate and minify node_modules into vendor.js, this way you can still possibly debug main.js in AWS console, as long as your own code is not too big for for AWS console If you're seeing issues, let me know :) BREAKING CHANGE: We're now using Webpack 5. If you're seeing anything weird, let us know.
1 parent 67eae6d commit 220700e

File tree

3 files changed

+2876
-2684
lines changed

3 files changed

+2876
-2684
lines changed

package.json

Lines changed: 28 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -43,33 +43,33 @@
4343
"trailingComma": "all"
4444
},
4545
"dependencies": {
46-
"@babel/core": "7.10.5",
47-
"@babel/plugin-transform-runtime": "7.10.5",
48-
"@babel/preset-env": "7.10.4",
49-
"babel-loader": "8.1.0",
50-
"babel-plugin-source-map-support": "2.1.2",
46+
"@babel/core": "7.12.13",
47+
"@babel/plugin-transform-runtime": "7.12.15",
48+
"@babel/preset-env": "7.12.13",
49+
"babel-loader": "8.2.2",
50+
"babel-plugin-source-map-support": "2.1.3",
5151
"cross-spawn": "7.0.3",
5252
"find-up": "5.0.0",
5353
"noop2": "2.0.0",
5454
"source-map-support": "0.5.19",
55-
"ts-loader": "8.0.1",
56-
"webpack": "4.44.0",
57-
"webpack-cli": "3.3.12"
55+
"ts-loader": "8.0.16",
56+
"webpack": "5.21.2",
57+
"webpack-cli": "4.5.0"
5858
},
5959
"devDependencies": {
60-
"@aws-cdk/aws-lambda": "1.54.0",
61-
"@aws-cdk/core": "1.54.0",
60+
"@aws-cdk/aws-lambda": "1.89.0",
61+
"@aws-cdk/core": "1.89.0",
6262
"@types/cross-spawn": "6.0.2",
63-
"@typescript-eslint/eslint-plugin": "^3.7.1",
64-
"@typescript-eslint/parser": "^3.7.1",
65-
"eslint-plugin-import": "2.22.0",
66-
"eslint-plugin-jest": "23.18.0",
67-
"prettier-plugin-packagejson": "2.2.5",
68-
"semantic-release": "^17.1.1",
69-
"semantic-release-cli": "5.3.1",
70-
"tsdx": "0.13.2",
71-
"tslib": "2.0.0",
72-
"typescript": "3.9.7"
63+
"@typescript-eslint/eslint-plugin": "^4.15.0",
64+
"@typescript-eslint/parser": "^4.15.0",
65+
"eslint-plugin-import": "2.22.1",
66+
"eslint-plugin-jest": "24.1.3",
67+
"prettier-plugin-packagejson": "2.2.9",
68+
"semantic-release": "^17.3.8",
69+
"semantic-release-cli": "5.4.3",
70+
"tsdx": "0.14.1",
71+
"tslib": "2.1.0",
72+
"typescript": "4.1.4"
7373
},
7474
"peerDependencies": {
7575
"@aws-cdk/aws-lambda": "^1.54.0",
@@ -105,18 +105,23 @@
105105
"import/order": [
106106
"error",
107107
{
108-
"newlines-between": "always"
108+
"newlines-between": "always",
109+
"alphabetize": {
110+
"order": "asc"
111+
}
109112
}
110113
]
111114
},
112115
"settings": {
113116
"import/extensions": [
114-
".ts"
117+
".ts",
118+
".js"
115119
],
116120
"import/resolver": {
117121
"node": {
118122
"extensions": [
119-
".ts"
123+
".ts",
124+
".js"
120125
]
121126
}
122127
}

src/NodejsFunction.ts

Lines changed: 119 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import * as fs from "fs";
2-
import * as path from "path";
32
import * as os from "os";
3+
import * as path from "path";
44
import * as process from "process";
5-
import * as spawn from "cross-spawn";
6-
import findUp from "find-up";
75

86
import * as lambda from "@aws-cdk/aws-lambda";
97
import * as cdk from "@aws-cdk/core";
8+
import * as spawn from "cross-spawn";
9+
import findUp from "find-up";
1010

1111
/**
1212
* Properties for a NodejsFunction
@@ -95,53 +95,61 @@ export class NodejsFunction extends lambda.Function {
9595
);
9696
const webpackConfigPath = path.join(outputDir, "webpack.config.js");
9797

98-
// The code below is mostly to handle cases where this module is used through
99-
// yarn link. I think otherwise just using require.resolve and passing just the babel plugin
100-
// names would have worked.
101-
102-
const webpackBinPath = require.resolve("webpack-cli");
98+
const webpackBinPath = require.resolve("webpack-cli/bin/cli.js", {
99+
paths: [__dirname],
100+
});
103101

104-
const plugins = [
105-
"webpack",
106-
"babel-loader",
107-
"@babel/preset-env",
108-
"@babel/plugin-transform-runtime",
109-
"babel-plugin-source-map-support",
110-
"noop2",
111-
];
112102
const pluginsPath = path.join(
113103
webpackBinPath.slice(0, webpackBinPath.lastIndexOf("/node_modules")),
114104
"node_modules",
115105
);
116-
const pluginsPaths: any = plugins.reduce(function(acc, pluginName) {
117-
return {
118-
[pluginName]: findUp.sync(pluginName, {
119-
type: "directory",
120-
cwd: pluginsPath,
121-
}),
122-
...acc,
123-
};
124-
}, {});
106+
107+
const pluginsPaths = {
108+
webpack: findModulePath("webpack", pluginsPath),
109+
"babel-loader": findModulePath("babel-loader", pluginsPath),
110+
"@babel/preset-env": findModulePath("@babel/preset-env", pluginsPath),
111+
"@babel/plugin-transform-runtime": findModulePath(
112+
"@babel/plugin-transform-runtime",
113+
pluginsPath,
114+
),
115+
"babel-plugin-source-map-support": findModulePath(
116+
"babel-plugin-source-map-support",
117+
pluginsPath,
118+
),
119+
noop2: findModulePath("noop2", pluginsPath),
120+
"terser-webpack-plugin": findModulePath(
121+
"terser-webpack-plugin",
122+
pluginsPath,
123+
),
124+
};
125125

126126
// NodeJs reserves '\' as an escape char; but pluginsPaths etc are inlined directly in the
127127
// TemplateString below, so will contain this escape character on paths computed when running
128128
// the Construct on a Windows machine, and so we need to escape these chars before writing them
129-
const escapePathForNodeJs = (path: string) => path.replace(/\\/g, '\\\\');
129+
const escapePathForNodeJs = (path: string) => {
130+
return path.replace(/\\/g, "\\\\");
131+
};
130132

131133
const webpackConfiguration = `
132134
const { builtinModules } = require("module");
133-
const { NormalModuleReplacementPlugin } = require("${
134-
escapePathForNodeJs(pluginsPaths["webpack"])
135-
}");
135+
const { NormalModuleReplacementPlugin } = require("${escapePathForNodeJs(
136+
pluginsPaths["webpack"],
137+
)}");
138+
const TerserPlugin = require("${escapePathForNodeJs(
139+
pluginsPaths["terser-webpack-plugin"],
140+
)}");
136141
137142
module.exports = {
138-
mode: "none",
143+
name: "aws-lambda-nodejs-webpack",
144+
mode: "production",
139145
entry: "${escapePathForNodeJs(entryFullPath)}",
140146
target: "node",
141147
resolve: {
142-
modules: ["node_modules", "."],
143-
extensions: [ '.ts', '.js' ],
148+
// next line allows resolving not found modules to local versions (require("lib/log"))
149+
modules: ["node_modules", "${escapePathForNodeJs(process.cwd())}"],
150+
extensions: [ ".ts", ".js" ],
144151
},
152+
context: "${escapePathForNodeJs(process.cwd())}",
145153
devtool: "source-map",
146154
module: {
147155
rules: [
@@ -151,6 +159,7 @@ export class NodejsFunction extends lambda.Function {
151159
use: {
152160
loader: "${escapePathForNodeJs(pluginsPaths["babel-loader"])}",
153161
options: {
162+
cwd: "${escapePathForNodeJs(process.cwd())}",
154163
cacheDirectory: true,
155164
presets: [
156165
[
@@ -167,19 +176,58 @@ export class NodejsFunction extends lambda.Function {
167176
]
168177
],
169178
plugins: [
170-
"${escapePathForNodeJs(pluginsPaths["@babel/plugin-transform-runtime"])}",
171-
"${escapePathForNodeJs(pluginsPaths["babel-plugin-source-map-support"])}"
179+
"${escapePathForNodeJs(
180+
pluginsPaths["@babel/plugin-transform-runtime"],
181+
)}",
182+
"${escapePathForNodeJs(
183+
pluginsPaths["babel-plugin-source-map-support"],
184+
)}"
172185
]
173186
}
174187
}
175188
},
176189
{
177190
test: /\\.ts$/,
178-
use: 'ts-loader',
191+
use: {
192+
loader: "${escapePathForNodeJs(
193+
findModulePath("ts-loader", pluginsPath),
194+
)}",
195+
options: {
196+
context: "${escapePathForNodeJs(process.cwd())}",
197+
configFile: "${escapePathForNodeJs(
198+
path.join(process.cwd(), "tsconfig.json"),
199+
)}",
200+
transpileOnly: true
201+
}
202+
},
179203
exclude: /node_modules/,
180204
},
181205
]
182206
},
207+
cache: {
208+
type: "filesystem",
209+
buildDependencies: {
210+
// force the config file to be this current file, since it won't change over builds
211+
// while the temporary webpack config file used would change, thus disabling webpack cache
212+
config: ["${escapePathForNodeJs(__filename)}"]
213+
}
214+
},
215+
optimization: {
216+
splitChunks: {
217+
cacheGroups: {
218+
vendor: {
219+
chunks: "all",
220+
filename: "vendor.js", // put all node_modules into vendor.js
221+
name: "vendor",
222+
test: /node_modules/,
223+
},
224+
},
225+
},
226+
minimize: true,
227+
minimizer: [new TerserPlugin({
228+
include: "vendor.js" // only minify vendor.js
229+
})],
230+
},
183231
externals: [...builtinModules, "aws-sdk"],
184232
output: {
185233
filename: "[name].js",
@@ -200,13 +248,23 @@ export class NodejsFunction extends lambda.Function {
200248

201249
fs.writeFileSync(webpackConfigPath, webpackConfiguration);
202250

203-
// to implement cache, create a script that uses webpack API, store cache in a file with JSON.stringify, based on entry path key then reuse it
204-
const webpack = spawn.sync(webpackBinPath, ["--config", webpackConfigPath], {
205-
cwd: process.cwd(),
206-
});
251+
// console.time("webpack");
252+
const webpack = spawn.sync(
253+
webpackBinPath,
254+
["--config", webpackConfigPath],
255+
{
256+
// we force the CWD to aws-lambda-nodejs-webpack root, otherwise webpack-cli might resolve to the
257+
// user's project version (https://github.com/webpack/webpack-cli/blob/master/packages/webpack-cli/bin/cli.js)
258+
cwd: path.join(__dirname, ".."),
259+
},
260+
);
261+
// console.timeEnd("webpack");
207262

208263
if (webpack.status !== 0) {
209-
console.error(`webpack had an error when bundling. Return status was ${webpack.status}`);
264+
console.error(
265+
`webpack had an error when bundling. Return status was ${webpack.status}`,
266+
);
267+
console.error(webpack.error);
210268
console.error(
211269
webpack?.output?.map(out => {
212270
return out?.toString();
@@ -216,8 +274,6 @@ export class NodejsFunction extends lambda.Function {
216274
process.exit(1);
217275
}
218276

219-
fs.unlinkSync(webpackConfigPath);
220-
221277
super(scope, id, {
222278
...props,
223279
runtime,
@@ -237,3 +293,24 @@ export class NodejsFunction extends lambda.Function {
237293
function nodeMajorVersion(): number {
238294
return parseInt(process.versions.node.split(".")[0], 10);
239295
}
296+
297+
// this method forces resolving plugins relative to node_modules/aws-lambda-nodejs-webpack
298+
// otherwise they would be resolved to the user versions / undefined versions
299+
function findModulePath(moduleName: string, pluginsPath: string) {
300+
try {
301+
return require.resolve(moduleName);
302+
} catch (error) {
303+
const modulePath = findUp.sync(moduleName, {
304+
type: "directory",
305+
cwd: pluginsPath,
306+
});
307+
308+
if (modulePath === undefined) {
309+
throw new Error(
310+
`Can't find module named ${moduleName} via require.resolve or find-up`,
311+
);
312+
}
313+
314+
return modulePath;
315+
}
316+
}

0 commit comments

Comments
 (0)