11import * as fs from "fs" ;
2- import * as path from "path" ;
32import * as os from "os" ;
3+ import * as path from "path" ;
44import * as process from "process" ;
5- import * as spawn from "cross-spawn" ;
6- import findUp from "find-up" ;
75
86import * as lambda from "@aws-cdk/aws-lambda" ;
97import * 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 {
237293function 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