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
{
  // 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
  mode: args.mode || "development",
  devtool: args.mode == "production" ? "source-map" : "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: "./src/index.js",
  output: {
    // The default value will extracted from the name in "package.json"
    // Can be overwritten by the cc-webpack configuration.
    filename: 'my-library.js',
    // The chunkFilename is defined for dynamic imports.
    // The prfix can be defined in the cc-webpack configuration.
    chunkFilename: 'parts/[name].js',
    // The destination.
    path: path.join(process.cwd(), "dist"),
    // If we do not set this in Webpack 5 the dynamic imports
    // will always be prefixed by the word "auto".
    publicPath: ""
  },

  // 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: {
    stats: "errors-warnings",
    contentBase: path.join(process.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)
        // and optionally the favicon source file (if
        // staticParser.keepFaviconSource was set to "false").
      ],
    },

    // To put the parsed CSS files in a separate file:
    MiniCssExtractPlugin {
      options: {
        filename: 'my-library.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 favoicons. 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: 'favicon/',
        logo: './static/logo.png'
      }
    },

    // 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: finalBoilerplateConfig.bundle.getCdnPath,
    },

    // Banner Plugin will be used only in production mode
    BannerPlugin({
      banner: finalBoilerplateConfig.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
      { test: /\.vue$/, exclude: /node_modules/, loader: 'vue-loader' },

      // *.html: html-loader
      // *.ejs.html: ejs-loader
      // This is to import (statically or dynamic) of HTML files.
      // With the extension "*.ejs.html" EJD variables can be used.
      // For now images or other external assets will not be considered.
      {
        test: /\.html$/,
        oneOf: [
          {
            test: /\.ejs.html$/,
            exclude: /node_modules/,
            loader: "ejs-loader",
            options: {
              variable: "data"
            }
          },
          {
            exclude: /node_modules/,
            loader: "html-loader",
            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$/,
        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 > css-loader > extract-plugin
      // Postcss will translate them into browser compatible CSS
      // and css-loader include them (and their dependecies) into
      // the final build. For now images or other url targets
      // will not be processed.
      // 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$/],
        exclude: /node_modules/,
        use: [
          MiniCssExtractPlugin.loader,
          {
            loader: 'css-loader',
            options: {
              url: false,
              modules: {
                mode: (ressourcePath) => {
                  //returns "global" for module.css and module-global.css files
                  //returns "local" for module-local.css files
                  //returns "pure" module-pure.css files
                },
                auto: /\.module(-(global|local|pure))?\.icss$/
              },
              importLoaders: 1
            }
          },
          'postcss-loader'
        ]
      },

      // *.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.
          }
        ]
      }
    ]
  }
}