A collection of standard configurations and helpers for webpack.
webpack.config.js file ended with the following:
javascript
module.exports = mode => configuration.get(mode);
`
Webpack-helper v3 and beyond: Your webpack.config.js file should end with the following (as is also mentioned further below):
`javascript
module.exports = (env, details) => configuration.get(details.mode);
`
Quick Start
`bash
npm i -D @studyportals/webpack-helper
`
After installation, getting started is quite easy. Just set up a webpack.config.js file in the root of your project, require Webpack Helper and set up a configuration using it's create method like so:
`javascript
const path = require('path');
const WebpackHelper = require("@studyportals/webpack-helper");
const configuration = WebpackHelper.create({
entry: [
"./src/main.ts"
],
output: {
path: path.resolve(__dirname, './dist'),
fileName: 'bundle',
hash: true,
// Use this if you want to control hashing for js and css separately
hash: {
js: true,
css: false
}
},
preset: "vue-typescript",
bob: {
name: 'MicroService',
html: '',
baseURL: '/dist',
defer: ['style'],
dllPackages: [
{
name: "package-dll",
manifest: require("@studyportals/package-dll/dist/manifest.json")
}
]
},
common: {
//any config
},
production: {
//any config
}
});
`
When your configuration is created you can get an instance of it for the correct environment and export it for webpack to use like so:
`javascript
module.exports = (env, details) => configuration.get(details.mode);
`
SCSS Migration from 1.53.x to >= 1.54.0
This package is used by other services that build with sass. Each sass release is a JavaScript build of the same-numbered Dart Sass compiler, so bumping from 1.53.x to 1.54.0 silently enables a raft of breaking-change warnings. Eventually when Dart Sass 3 is released, these warnings become hard errors that will break downstream builds.
| Dependency in package.json | Real compiler behind the scenes | Notes |
| ---------------------------- | ------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------- |
| "sass": "^1.89.2" | Dart Sass 1.89.2 (JS build) | Running npx sass --version shows the exact Dart Sass version. |
| "sass-loader": "^16" | Webpack loader that invokes whatever Sass implementation you installed (defaults to the sass package above). | |
$3
| Change (see checklist below) | First warns in | Becomes an error |
| ---------------------------------------------- | ------------------ | -------------------- |
| Invalid combinators (> >, leading +, etc.) | 1.54.0 | already errors |
| Media-Query Level 4 logic | 1.54.0 | 1.56.0 |
| Strict unary + - spacing | 1.55.0 | already errors |
| Duplicate !default/!global flags | 1.62.0 | already errors |
| Reserved type() function | 1.86.0 | next major |
| / division → math.div() | 1.33.0 | next major |
| @import → module system | 1.80.0 | Dart Sass 3 |
$3
To help us migrate, there is a package called sass-migrator that you can use to resolve the majority of the issues.
`bash
npm install --save-dev sass-migrator
`
Then run
`bash
migrate @import → @use/@forward
npx sass-migrator module --migrate-deps "src/*/.scss"
replace “/” division with math.div()
npx sass-migrator division --migrate-deps "src/*/.scss"
add the required space for ambiguous + / - operators
npx sass-migrator strict-unary --migrate-deps "src/*/.scss"
swap legacy color helpers (lighten(), darken(), …) for sass:color APIs
npx sass-migrator color --migrate-deps "src/*/.scss"
`
#### List of changes
| # | Change you must make | Before — old SCSS | After — new SCSS | Why it matters |
| -- | ----------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- |
| 1 | Replace @import → @use/@forward | A) @import "tools/colors";
B) @import "theme/light";
@import "components/button"; | A) @use "tools/colors" as colors;
B) @use "theme/light" as *;
@use "components/button"; | @import builds a huge global namespace and re-parses files; removed in Dart Sass 3.0. |
| 2 | Slash-division → math.div() | A) width: 100%/3;
B) $col: 940px/12; | @use "sass:math";
A) width: math.div(100%, 3);
B) $col: math.div(940px, 12); | / is being re-defined as a list separator. |
| 3 | Make unary + / - unambiguous | A) gap: 10px -$space;
B) margin: +$n ($n2); | A) gap: 10px - $space; or gap: 10px (-$space);
B) margin: + $n (#{$n2}); | Compiler now errors on ambiguous signs. |
| 4 | Rename a custom type() function | A) @function type($x) {…}
B) $v: type(42); | A) @function kind($x) {…}
B) $v: kind(42); | CSS adds its own reserved type(). |
| 5 | Swap legacy colour helpers for sass:color | A) $hover: lighten($brand, 10%);
B) $new: adjust-hue($brand, 20deg); | @use "sass:color";
A) $hover: color.adjust($brand, $lightness: 10%);
B) $new: color.adjust($brand, $hue: 20deg); | Old helpers assume sRGB only. |
| 6 | Remove duplicate flags | A) $pad: 1rem !default !default;
B) $debug: null !global !global; | $pad: 1rem !default;
$debug: null !global; | A second !default/!global is now an error. |
| 7 | Delete meta.feature-exists() | A) @use "sass:meta";
@if meta.feature-exists("global-variable-shadowing") {…}
B) $has: feature-exists(calc); | Usually just delete — all features it checks are always present. | Function is obsolete. |
| 8 | Fix stray / doubled combinators | A) + .alert {…}
B) nav > > li {…} | A) .alert {…}
B) nav > li {…} | Invalid CSS selectors now error. |
| 9 | Keep declarations before nested rules | .card {
color:#333;
&__header {…}
font-weight:bold;
} | .card {
color:#333;
&__header {…}
& { font-weight:bold; }
} | Mirrors CSS Nesting source-order rules. |
| 10 | Use CSS-4 logic in @media | A) @media (not (prefers-reduced-motion)) {…}
B) @media ((hover) and (pointer:fine)) {…} | @media not (prefers-reduced-motion) {…}
@media (hover) and (pointer:fine) {…} | Old parenthesised syntax conflicts with MQ L4. |
| 11 | Always interpolate custom-prop values | A) --accent: $brand;
B) --size: $gap 1.5; | --accent: #{$brand};
--size: #{$gap 1.5}; | Stops Sass mistaking raw CSS tokens for vars. |
| 12 | Give colour funcs the right units | A) hsl(0.5turn,100%,50%);
B) $deg: 90grad; hue($c,$deg); | A) hsl(180deg,100%,50%);
B) $deg: 90deg; color.adjust($c,$hue:$deg); | Functions now enforce CSS-spec units. |
| 13 | Use math.abs() for percentages | A) $pos: abs(-10%);
B) padding: abs(5%); | @use "sass:math";
A) $pos: math.abs(-10%);
B) padding: math.abs(5%); | Global abs() drops % support. |
| 14 | Rename any name starting -- | A) @mixin --theme($c){…}
B) @function --shadow(){…} | @mixin _theme($c){…}
@function _shadow(){…} | -- reserved for future native CSS mixins. |
| 15 | Remove special @-moz-document parser | @-moz-document url-prefix("https://") { … }
@-moz-document domain(mozilla.org) { … } | (Delete or treat as an unknown at-rule; only @-moz-document url-prefix() hack still compiles.) | Feature gone from Firefox → dropped from Sass. |
| 16 | Don’t @extend a compound selector | A) .notice { @extend .msg.info; }
B) .alert { @extend .box.error; } | .notice { @extend .msg, .info; }
.alert { @extend .box, .error; } | Dart Sass forbids extending .foo.bar. |
| 17 | You can’t overwrite a mixin or function that came in via @use | @import "~@studyportals/styles-abstracts/abstracts.scss";
@include TextStyle($name);
font-weight: 700;| @use "~@studyportals/styles-abstracts/abstracts.scss" as *;
@include TextStyle($name);| Error immediately since @use (1.23) — name collisions are blocked ([sass-lang.com][1]) |
[1]: https://sass-lang.com/blog/the-module-system-is-launched/ "Sass: The Module System is Launched"
Chunks Retry
Webpack Helpers has a default configuration to retry chunks which cannot get loaded on the first try (for whatever reason, network or otherwise).
It has an Exponential Backoff to calculate the delay between retries of a maximum of 2 seconds.
The functionality is implemented thru a plugin called webpack-retry-chunk-load-plugin .
$3
By default it is enabled if the selected preset is vue-typescript and it uses 6 as the number of retries it does and it uses the rollbar instance on the window object (Portal instance) to log an error when it reaches the threshold of retries without being able to load the chunk (new Error('Failed to load chunk for after retries')).
$3
To configure the functionality you can provide an object following this interface
`js
interface IChunksRetryPluginConfig {
inject: boolean; //if you want the plugin injected
threshold: number; //amount of retries
thresholdReachedCallBack?: string; //a function (body of it as string) to be called when the amount of retries has been exhausted
rollbarInstanceIdentifier?: string; //a key of a Rollbar instance present on the 'window' object e.g if you want to use window['rollbar_PQ'] to track the error in the Profile Questionnaire project in Rollbar then you send 'rollbar_PQ'
disableDefaultRollbarError?: boolean; //if you don't want to log by default when the threshold has been reached
}
`
and you can provide this thru the webpack config of your service like so
`js
const configuration = WebpackHelper.create({
chunksRetryConfig: {
threshold: 10,
thresholdReachedCallBack: console.log("hello"),
},
...
`
$3
We have some limitations in this implementation based on the plugin we used and that the code will be part of the webpack exported code
$3
You can only write ES5 code inside the thresholdReachedCallBack
$3
The function thresholdReachedCallBack must be a string (aka the body of the function) and we cannot change that to be a function while retaining the simplicity of the code.
Configuration
Webpack Helper offers a compact but flexible configuration interface. Below you will find every option available.
$3
The entrypoint for your webpack bundle. This can either be configured as an object with the bundle name as a key and the path as a value or as an array of paths if your project requires multiple entry points.
`javascript
entry: [
"./src/main.ts"
]
//or
entry: {
bundle: "./src/main.ts"
}
`
$3
When working with packaged components and libraries it is required to keep the bundle name in line with the major version of the package. This helps to prevent reference issues when multiple versions of a package load on a single page.
`javascript
// version 1
entry: {
multiselect_v1: "./src/main.ts"
}
// version 2
entry: {
multiselect_v2: "./src/main.ts"
}
`
#### DLL
When working with a packaged component as a DLL, both the JS bundle and CSS bundle from the core package must be referenced, as below:
`javascript
entry: {
multiselect_v2: [
'@studyportals/multiselect/dist/multiselect.js',
'@studyportals/multiselect/dist/multiselect.css'
]
}
`
$3
The output object has some options that define how your bundle will be output. It has the following properties:
- path(required): The target directory for all output files.
- library: The name of the exported library, which will also be used to namespace sourcemaps. This will default to null.
- publicPath: The url to the output directory resolved relative to the HTML page. This will default to null.
- fileName: A custom filename for your bundle. This will default to [name] which refers to the name you've given your entrypoint.
- hash: Whether or not to add build hashes to your filenames. This will default to true. Whenever you want different values for your js and css you can pass an object with separate properties and values as in the example below.
- crossOriginLoading: Tells webpack to enable cross-origin loading of chunks. Can either be set to anonymous or user-credentials.
`javascript
output: {
path: path.resolve(__dirname, './dist'),
library: 'test-microservice',
publicPath: '/assets/',
fileName: 'application',
crossOriginLoading: 'anonymous',
hash: false,
//or
hash: {
js: true,
css: false
}
}
`
$3
The preset property determines which preset to load into your webpack configuration.
`javascript
preset: "vue-typescript | typescript | javascript"
`
$3
The bob object adds all necessary configuration to have your project generate a manifest file to be used by Bob.
- name(required): The name of your microservice.
- html(required): The HTML that needs to be output by your microservice. Can be a path to a document or a snippet.
- baseURL: The baseURL for your microservice assets.
- async: An array of JS filenames to be loaded async.
- defer: An array of CSS filenames to be lazy-loaded.
- exclude: An array of filenames (CSS and JS) that should be excluded from your manifest file.
- dllPackages: An array of objects that reference your Dll dependencies. They all require a name and a Dll manifest in the form of a json file.
`javascript
bob: {
name: 'MicroService',
html: '',
baseURL: '/dist',
defer: ['style'],
exclude: ['demo-assets'],
dllPackages: [
{
name: "package-dll",
manifest: require("@studyportals/package-dll/dist/manifest.json")
}
]
}
`
WebpackHelper uses BobManifestGenerator to generate a manifest file.
$3
Extra Webpack configuration to be used for all environments can be passed in the common object. This configuration needs to adhere to Webpack's configuration interface and will be merged in with the other configuration through webpack-merge.
$3
Extra Webpack configuration to be used for all development builds can be passed in the development object. This configuration needs to adhere to Webpack's configuration interface and will be merged in with the other configuration through webpack-merge.
NOTE: Some default configuration for development builds will be added automatically. Details can be found here.
$3
Extra Webpack configuration to be used for all production builds can be passed in the production object. This configuration needs to adhere to Webpack's configuration interface and will be merged in with the other configuration through webpack-merge.
NOTE: Some default configuration for production builds will be added automatically. Details can be found here.
Presets
In order to make your life as easy as possible, Webpack Helper ships with a set of presets that will take care of putting some essential plugins and loaders in your configuration. The different presets are listed below.
More in depth information on what configuration these presets will add to your project can be found here.
$3
The javascript preset will resolve all .js and .json files and transpiles them with Babel. If your project has styling, it will separate that out in a separate .css file.
$3
The typescript preset will resolve all .ts, .tsx, .js and .json files. It transpiles them using tsc and Babel. Be aware that you will need to include a tsconfig.json in your project for it to work properly. If your project has styling, it will separate that out in a separate .css file.
$3
The vue-typescript preset will resolve all .vue, .ts, .tsx, .js and .json files. It transpiles them using vue-loader, tsc and Babel. Be aware that you will need to include a tsconfig.json in your project for it to work properly. If your project has styling, it will separate that out in a separate .css file.
DoD compliance with Babel and Browserslist
In order to comply to Studyportals' definition of done we use babel to transpile our front-end code according to the browsers we need to support. To do this your project needs some configuration. When Webpack Helper is installed it will put standard configuration in the root of your project which consists of 2 files:
- .babelrc: A standard configuration for babel that loads the babel-env and babel-typescripts presets. You might need to add more plugins for your specific project.
- .browserslistrc: A configuration file for browserslist. This is used by babel-env to determine which browsers to transpile the code for. Within this file the configuration for student facing products will be loaded by default but other configurations can be easily loaded by uncommenting some lines.
Unit testing
From version 6.0.0` onwards test dependencies are no longer included and should be installed separately. It's also recommended to replace jest with vitest due the complex nature of package jungle. A upgrade guide can be find here