Skip to content

CodeCoupler Webpack Development Details: Generation of Webpack Configuration

The essential task of cc-webpack is to generate a Webpack configuration on the fly and start build and packaging. The generated and used Webpack configuration works as follows:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
{

  // "cwd" is by default "process.cwd()" which can be overwritten
  // with the "--cwd" option (from Version 3)

  // The assetFilter mute the perfomance counter for static assets,
  // favicons and external libraries:
  performance: { assetFilter: [Function: assetFilter] },

  // To keep the code clean we should import all other filetypes
  // including their extensions:
  resolve: {
    extensions: [ '.js', '.ts' ]
  },

  // Use the arguments to define mode and source-map generation
  // Till 3.1 `devtool: "source-map"` was used in production mode
  // From 3.7 devtool value can be overriden by argument `--devtool`
  mode: args.mode || "development",
  devtool: args.devtool ??
      (args.mode == "production" ? undefined : "eval-source-map"),

  // This was added because otherwise the banner from the BannerPlugin will
  // be removed and placed into a separate file "<output.filename>.LICENSE.txt"
  optimization: {
    minimizer: [
      new TerserPlugin({
        extractComments: false
      })
    ]
  },

  // Use index.js as entry and bundle everything in one file.
  // Remark: Versions before 1.5 parsed each js and ts file
  // with entry: glob.sync("./src/**/*.@(js|ts)")
  entry: path.join(cwd, "src/index.js"),
  output: {
    // The default value will extracted from the name in "package.json"
    // The name can be overwritten by the cc-webpack configuration.
    // Appending a contenthash can be disabled by the cc-webpack configuration.
    filename: 'my-library.[contenthash].js',
    // The chunkFilename is defined for dynamic imports.
    // The prefix "parts" can be defined in the cc-webpack configuration.
    chunkFilename: 'parts/[name].js',
    // The destination.
    path: path.join(cwd, "dist"),
    // If we do not set this in Webpack 5 the dynamic imports
    // will always be prefixed by the word "auto". Should be
    // overwritten in SPA-Vue-Applications to "/".
    publicPath: "",
    // From version 3:
    // The destination of imported assets. the function will keep
    // the folder structure of the assets and add a hash to the filename.
    assetModuleFilename: Function,
    // Till version 3.3.0 it was just: `library: Get from config bundle.libraryName`
    // With this the library could be used with script tags. However, if you wanted
    // to bundle the library into your own project, there was the problem that the
    // exports did not work properly (`import { `something` } from "myLibrary"` was
    // always undefined). Therefore, the library had to be externalized in the
    // follow-up project as well. That's why the type "umd" was added from version 3.3.1
    // onwards. However, this led to problems with resolving the library's externals.
    // For example, the library looked for the "$" module instead of the "jquery" module.
    // We told the library that the "jquery" module should be resolved using the globaly
    // variable "$". So we had to add the setting `externalsType: "global"` for this.
    library: {
        name: Get from config bundle.libraryName,
        type: "umd",
    }
  },
  // See above in setting "output.library".
  externalsType: "global",

  // Make the console output cleaner:
  stats: "errors-warnings",

  //This setting was inserted to work the full page reload
  //of the webpack-dev-server on source changing. The new
  //default value "browserlist" of webpack 5 will break
  //this feature: https://github.com/webpack/webpack-dev-server/issues/2758#issuecomment-708144038
  //Even the new feature to set here an array will break
  //the feature: https://github.com/webpack/webpack-dev-server/issues/2758#issuecomment-779445432
  target: "web",

  // This is the default configuration of the devServer:
  devServer: {
    //Removed in Version 3:
    //stats: "errors-warnings",
    contentBase: path.join(cwd, "dist"),
    open: true
  },

  // Now the plugins:
  plugins: [

    // To clean up dist folder:
    CleanWebpackPlugin {},

    // Just a fancy progressbar
    WebpackBarPlugin {},

    // Copy static files which do not need injection:
    CopyPlugin {
      patterns: [
        // Here a list of all static files will be generated
        // except:
        // - HTML files (defined in staticParser.htmlTemplates),
        // - favicon source file (if staticParser.keepFaviconSource was set to "false"),
        // - inc folder (if staticParser.keepIncSource was set to "false"),
        // - manifest override file (if staticParser.keepManifestSource was set to "false"),
      ],
    },

    // To put the parsed CSS files in a separate file:
    // The name can be overwritten by the cc-webpack configuration.
    // Appending a contenthash can be disabled by the cc-webpack configuration.
    MiniCssExtractPlugin {
      options: {
        filename: 'my-library.[contenthash].css',
        // The chunkFilename is defined for dynamic imports.
        // The prefix "parts" can be defined in the cc-webpack configuration.
        chunkFilename: `parts/[name].css`,
      }
    },

    // For linting CSS files:
    new StyleLintPlugin({
      files: "./src/**/*.css"
    }),

    // This plugin allows to set the publicPath for dynamic imports
    // into a global variable. With this you can install your library
    // anywhere you want and the dynamic imports will work:
    WebpackRequireFrom {
      options: { variableName: 'pathToMyLibrary', suppressErrors: true },
    },

    // Needed to load vue files
    VueLoaderPlugin {},

    // Parse all HTML files in static folder.
    //
    // The properties "inject" and "scriptLoading" can be defined
    // in the CodeCoupler Webppack Configuration "webpack.cc-config.js".
    //
    // Note on "template":
    // By default the HtmlWebPlugin use the defined webpack loader.
    // But this loader (html-loader) do not parse variable names like
    // "<%= htmlWebpackPlugin.tags.headTags %>". We could define a
    // loader chain like described here: https://github.com/webpack-contrib/html-loader/issues/195
    // or force to use the "ejs-loader" like done here.
    //
    // Note on "scriptLoading":
    // The new value starting with webpack 5 and the corresponding plugin
    // version is "defer". It is ok to set this value to this value, but
    // you have to know that you cannot start executing scripts in your
    // "index.js", but you have to wait the DOM is loaded. Furthermore
    // approximately 2% of the browsers out there do not understand this
    // attribute. So we stay for now injecting the scripts to the end of the
    // body and not use the "defer" attribute.
    HtmlWebpackPlugin {
      options: {
        template: '!!ejs-loader?esModule=false!./static/index.html',
        filename: 'index.html',
        inject: true,
        scriptLoading: 'blocking'
      },
    },
    // ... this will be repeated for each HTML file

    // Generate favicons and Manifest. Inject the links to them always
    // in all HTML files parsed by HtmlWebpackPlugin (which will be all the
    // files in the static folder). Logo and prefix can be configured
    // in cc-webpack configuration:
    FaviconsWebpackPlugin {
      options: {
        inject: 'force',
        prefix: Get from config bundle.faviconSubfolder,
        logo: Get from config staticParser.favicon,
        manifest: Get from config staticParser.manifest
      }
    },

    // This plugin will set the webpack "externals" option
    // and furthermore copy the needed libraries to the output folder
    // and inject HTML files with the needed tags.
    ccWebpackExternalsPlugin {
      useCdn: args.externalsCdn ? true : false,
      getCdnPath: Get from config bundle.getCdnPath,
    },

    // Banner Plugin will be used only in production mode
    BannerPlugin({
      banner: Get from config production.banner,
    }),

    // The new way to integrate eslint in Webpack 5 instead
    // using eslint-loader:
    new ESLintPlugin({
      extensions: ["js", "ts"],
    })

  ],

  // Now the modules:
  module: {
    rules: [
      // *.vue: vue-loader
      // The option { hotReload: false } was added in version 3 because the Hot Module Replacement
      // of the devServer collides with the Hot Module Replacement of the loader.
      {
        test: /\.vue$/,
        //Removed in Version 2.5
        //exclude: /node_modules/,
        loader: 'vue-loader',
        options: { hotReload: false }
      },

      // *.html: html-loader
      // *.template.html: ejs-loader
      // *.ejs.html: ejs-compiled-loader
      // This is to import (statically or dynamic) of HTML files.
      // With the extension "*.ejs.html" EJD variables can be used.
      // Version 2: Images or other external assets will not be considered.
      // Version 3: Images or other external assets will be considered.
      {
        test: /\.html$/,
        oneOf: [
          {
            test: /\.template.html$/i,
            loader: "ejs-loader",
            options: {
              variable: "locals",
            },
          },
          {
            test: /\.ejs.html$/,
            //Removed in Version 2.5
            //exclude: /node_modules/,
            loader: "ejs-compiled-loader",
            options: {
              variable: "data"
            }
          },
          {
            exclude: /node_modules/,
            loader: "html-loader",
            //Removed in Version 3
            // options: {
            //   sources: false
            // }
          }
        ]
      },

      // *.ts: (eslint over its plugin) > ts > babel
      // ESLint will use the special configuration from ".eslintrc".
      // The ts-loader is prepared to work with vue files with <script lang="ts">
      // The ts-compiler will translate them into JavaScript and babel
      // will translate them into browser compatible JavaScript:
      {
        test: /\.ts$/,
        //Removed in Version 2.5
        //exclude: /node_modules/,
        use: [
          {
            loader: 'babel-loader',
            //In Versions before 1.5 "options" was used to specify
            //the babel configuration dynamically. This now will be
            //done by the .babelrc file.
          },
          {
            loader: 'ts-loader',
            options: { appendTsSuffixTo: [ /.vue$/ ] }
          }
        ]
      },

      // *.css, *.postcss, *.s[ac]ss:
      //    sass-loader > postcss (with autprefixer)
      //    > css-loader > extract-plugin
      //
      // sass-loader: (From Version 3 only) Loads a SASS/SCSS file
      // and compiles it to CSS
      //
      // postcss: Translate them into browser compatible CSS. From
      // Version 3.1: Add Vendor Prefixes
      //
      // css-loader: Include them (and their dependecies) into
      // the final build. Version 2: Images or other url targets
      // will not be processed. Version 3: Interprets `@import`
      // and `url()` like `import/require()`
      //
      // The condition /\.postcss$/ is used for vue files containing
      // a <style lang="postcss"> tag. The lang attribute is used for
      // syntax highlighting and validating of Vetur.
      //
      // Enables CSS Module compilation for files ending with
      // "modules.css", "modules-global.css", "modules-local.css" and
      // "modules-pure.css"
      {
        test: [/\.css$/, /\.postcss$/, /\.s[ac]ss$/i],
        exclude: /node_modules/,
        use: [
          {
            loader: MiniCssExtractPlugin.loader,
            options: {
              // From Version 3.4:
              // This option is important for vue css modules <style module>.
              // Otherwise a warning will be thrown exort "exoprt 'default' not found (module has no
              // exports)"
              defaultExport: true
            }
          },
          {
            loader: 'css-loader',
            options: {
              //Removed in Version 3
              //url: false,
              modules: {
                mode: (ressourcePath) => {
                  //Return "pure" for vue files (From 3.4, If <style module> was used)
                  //Returns "global" for module.css and module-global.css files
                  //Returns "local" for module-local.css files
                  //Returns "pure" module-pure.css files
                  //All other returns "local" (Till 3.3 all other returned "false", which was not correct)
                },
                auto: (resourcePath, resourceQuery, resourceFragment) => {
                  // From 3.4: Return true if file ends with ".vue" and query includes "module=true"
                  // Otherwise returns if filename matching /\.module(-(global|local|pure))?\.css$/
              },
              importLoaders: 2
            }
          },
          {
            loader: 'postcss-loader',
            options: {
              postcssOptions: {
                plugins: [
                  // From Version 3.1:
                  autoprefixer
                ]
              }
            }
          },
          //From Version 3:
          {
            loader: "sass-loader",
            //From Version 3.8.0 switched to sass-embedded and modern-compiler
            options: { api: "modern-compiler" },
          },
        ]
      },

      // *.js: (eslint over its plugin) > babel
      // ESLint will use the special configuration from ".eslintrc".
      // Babel finally translate them into browser compatible JavaScript:
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: [
          {
            loader: 'babel-loader',
            //In Versions before 1.5 "options" was used to specify
            //the babel configuration dynamically. This now will be
            //done by the .babelrc file.
          }
        ]
      },

      // *.png, *.jpg: Extract all imported assets of the html-loader,
      // css-loader and the vue-loader
      {
        test: /\.(png|svg|jpg|jpeg|gif|woff|woff2|eot|ttf|otf)$/i,
        type: 'asset/resource',
      },

    ]
  }
}