1
1
import * as fs from "fs" ;
2
- import * as path from "path" ;
3
2
import * as os from "os" ;
3
+ import * as path from "path" ;
4
4
import * as process from "process" ;
5
- import * as spawn from "cross-spawn" ;
6
- import findUp from "find-up" ;
7
5
8
6
import * as lambda from "@aws-cdk/aws-lambda" ;
9
7
import * as cdk from "@aws-cdk/core" ;
8
+ import * as spawn from "cross-spawn" ;
9
+ import findUp from "find-up" ;
10
10
11
11
/**
12
12
* Properties for a NodejsFunction
@@ -95,53 +95,61 @@ export class NodejsFunction extends lambda.Function {
95
95
) ;
96
96
const webpackConfigPath = path . join ( outputDir , "webpack.config.js" ) ;
97
97
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
+ } ) ;
103
101
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
- ] ;
112
102
const pluginsPath = path . join (
113
103
webpackBinPath . slice ( 0 , webpackBinPath . lastIndexOf ( "/node_modules" ) ) ,
114
104
"node_modules" ,
115
105
) ;
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
+ } ;
125
125
126
126
// NodeJs reserves '\' as an escape char; but pluginsPaths etc are inlined directly in the
127
127
// TemplateString below, so will contain this escape character on paths computed when running
128
128
// 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
+ } ;
130
132
131
133
const webpackConfiguration = `
132
134
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
+ ) } ");
136
141
137
142
module.exports = {
138
- mode: "none",
143
+ name: "aws-lambda-nodejs-webpack",
144
+ mode: "production",
139
145
entry: "${ escapePathForNodeJs ( entryFullPath ) } ",
140
146
target: "node",
141
147
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" ],
144
151
},
152
+ context: "${ escapePathForNodeJs ( process . cwd ( ) ) } ",
145
153
devtool: "source-map",
146
154
module: {
147
155
rules: [
@@ -151,6 +159,7 @@ export class NodejsFunction extends lambda.Function {
151
159
use: {
152
160
loader: "${ escapePathForNodeJs ( pluginsPaths [ "babel-loader" ] ) } ",
153
161
options: {
162
+ cwd: "${ escapePathForNodeJs ( process . cwd ( ) ) } ",
154
163
cacheDirectory: true,
155
164
presets: [
156
165
[
@@ -167,19 +176,58 @@ export class NodejsFunction extends lambda.Function {
167
176
]
168
177
],
169
178
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
+ ) } "
172
185
]
173
186
}
174
187
}
175
188
},
176
189
{
177
190
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
+ },
179
203
exclude: /node_modules/,
180
204
},
181
205
]
182
206
},
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
+ },
183
231
externals: [...builtinModules, "aws-sdk"],
184
232
output: {
185
233
filename: "[name].js",
@@ -200,13 +248,23 @@ export class NodejsFunction extends lambda.Function {
200
248
201
249
fs . writeFileSync ( webpackConfigPath , webpackConfiguration ) ;
202
250
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");
207
262
208
263
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 ) ;
210
268
console . error (
211
269
webpack ?. output ?. map ( out => {
212
270
return out ?. toString ( ) ;
@@ -216,8 +274,6 @@ export class NodejsFunction extends lambda.Function {
216
274
process . exit ( 1 ) ;
217
275
}
218
276
219
- fs . unlinkSync ( webpackConfigPath ) ;
220
-
221
277
super ( scope , id , {
222
278
...props ,
223
279
runtime,
@@ -237,3 +293,24 @@ export class NodejsFunction extends lambda.Function {
237
293
function nodeMajorVersion ( ) : number {
238
294
return parseInt ( process . versions . node . split ( "." ) [ 0 ] , 10 ) ;
239
295
}
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