diff --git a/components/Blog/Card/index.jsx b/components/Blog/Card/index.jsx new file mode 100644 index 0000000..db42ba5 --- /dev/null +++ b/components/Blog/Card/index.jsx @@ -0,0 +1,55 @@ +import styles from './index.module.css'; + +const formatDate = dateString => { + if (!dateString) return ''; + const date = new Date(dateString); + return date.toLocaleDateString('en-GB', { + day: 'numeric', + month: 'short', + year: 'numeric', + }); +}; + +export default function BlogCard({ + slug, + title, + date, + description, + contributors = [], +}) { + return ( +
+
+ + + +
+
{title || 'Untitled Post'}
+

{description}

+
+ +
+
+ {contributors.map(user => ( + + {user} + + ))} +
+ + + {formatDate(date)} +
+
+ ); +} diff --git a/components/Blog/Card/index.module.css b/components/Blog/Card/index.module.css new file mode 100644 index 0000000..f6c34ff --- /dev/null +++ b/components/Blog/Card/index.module.css @@ -0,0 +1,125 @@ +@reference "../../../styles/index.css"; + +.card { + @apply relative + flex + flex-col + h-full + rounded-xl + border + border-neutral-200 + bg-white + p-6 + overflow-hidden + transition-colors + duration-150 + hover:border-blue-300 + hover:bg-blue-50/40 + dark:border-neutral-800 + dark:bg-neutral-900 + dark:hover:border-blue-400/60 + dark:hover:bg-blue-950/40; +} + +.decoration { + @apply absolute + -right-8 + -top-8 + size-32 + bg-blue-500 + opacity-0 + transition-all + duration-500 + translate-x-4 + -translate-y-4; + + clip-path: polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%); +} + +.card:hover .decoration { + @apply opacity-5 + dark:opacity-10 + translate-x-0 + translate-y-0 + rotate-12; +} + +.mainLink { + @apply absolute + inset-0 + z-0 + focus:outline-none; +} + +.content { + @apply flex + flex-col + gap-3 + pb-5 + pointer-events-none; +} + +.title { + @apply m-0 + text-xl + font-bold + leading-tight + text-neutral-900 + dark:text-white; +} + +.description { + @apply m-0 + line-clamp-3 + text-base + leading-relaxed + text-neutral-600 + dark:text-neutral-400; +} + +.footer { + @apply mt-auto + flex + items-center + border-t + border-neutral-100 + pt-3 + dark:border-neutral-800/80; +} + +.avatars { + @apply flex + items-center + -space-x-2; +} + +.avatarLink { + @apply relative + z-10 + transition-transform + hover:-translate-y-1; +} + +.avatar { + @apply size-8 + rounded-full + border-2 + border-white + bg-neutral-100 + dark:border-neutral-900 + dark:bg-neutral-800; +} + +.dot { + @apply mx-3 + text-neutral-300 + dark:text-neutral-600; +} + +.date { + @apply tabular-nums + text-sm + font-medium + text-neutral-500 + dark:text-neutral-400; +} diff --git a/components/Layout.jsx b/components/Layout.jsx index 66c1b1c..ec3bfef 100644 --- a/components/Layout.jsx +++ b/components/Layout.jsx @@ -1,11 +1,13 @@ import DefaultLayout from '@node-core/doc-kit/src/generators/web/ui/components/Layout/index.jsx'; import HomeLayout from '../layouts/Home/index.jsx'; import SponsorsLayout from '../layouts/Sponsors/index.jsx'; +import BlogLayout from '../layouts/Blog/index.jsx'; import '../styles/index.css'; const LAYOUTS = { home: HomeLayout, sponsors: SponsorsLayout, + blogs: BlogLayout, }; export default function Layout(props) { diff --git a/layouts/Blog/index.jsx b/layouts/Blog/index.jsx new file mode 100644 index 0000000..a244dc5 --- /dev/null +++ b/layouts/Blog/index.jsx @@ -0,0 +1,40 @@ +import PartialArticle from '../PartialArticle/index.jsx'; +import BlogCard from '../../components/Blog/Card/index.jsx'; +import blogsData from '../../generated/blogs.json'; +import styles from './index.module.css'; + +/** + * Blogs page layout. Lists all Webpack blog posts in a grid, + * wrapped inside the PartialArticle shell to preserve the Sidebar and Navbar. + * + * @param {{ metadata: object }} props + */ +export default function BlogLayout({ metadata }) { + return ( + +
+
+
+

Webpack Blog

+

+ Latest news, updates, and deep dives from the Webpack team and + community. +

+
+
+ {blogsData.map(post => ( + + ))} +
+
+
+
+ ); +} diff --git a/layouts/Blog/index.module.css b/layouts/Blog/index.module.css new file mode 100644 index 0000000..4cff33f --- /dev/null +++ b/layouts/Blog/index.module.css @@ -0,0 +1,47 @@ +@reference "../../styles/index.css"; + +.blogSection { + @apply bg-white + py-16 + lg:py-20 + dark:bg-neutral-950; +} + +.container { + @apply mx-auto + max-w-7xl + px-6; +} + +.header { + @apply mb-12 + border-b + border-neutral-200 + pb-8 + text-center + dark:border-neutral-800; +} + +.mainTitle { + @apply mb-3 + text-3xl + font-bold + text-neutral-900 + lg:text-4xl + dark:text-white; +} + +.subtitle { + @apply mx-auto + max-w-2xl + text-lg + text-neutral-600 + dark:text-neutral-400; +} + +.grid { + @apply grid + gap-6 + sm:grid-cols-2 + lg:grid-cols-3; +} diff --git a/package.json b/package.json index 7acf629..c723445 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "build:prepare": "node scripts/prepare/index.mjs", "build:data": "npm-run-all build:data:*", "build:data:sponsors": "node scripts/data/sponsors.mjs", + "build:data:blog": "node scripts/data/blogs.mjs", "build:md": "npm-run-all build:md:*", "build:md:api": "node scripts/markdown/api.mjs", "build:md:readmes": "node scripts/markdown/readmes.mjs", diff --git a/pages/blogs/2020-10-10-webpack-5-release.md b/pages/blogs/2020-10-10-webpack-5-release.md new file mode 100644 index 0000000..549d27b --- /dev/null +++ b/pages/blogs/2020-10-10-webpack-5-release.md @@ -0,0 +1,1444 @@ +--- +title: Webpack 5 release +sort: 20201010 +contributors: + - sokra + - chenxsan +--- + +# Webpack 5 release + +Webpack 4 was released in February 2018. +Since then we shipped a lot of features without breaking changes. +We know that people dislike major changes with breaking changes. +Especially with webpack, which people usually only touch twice a year, and the remaining time it "just works". +But shipping features without breaking changes also has a cost: +We can't do major API or architectural improvements. + +So from time to time, there is a point where the difficulties pile up and we are forced to do breaking changes to not mess everything up. +That's the time for a new major version. +So webpack 5 contains these architectural improvements and the features that were not possible to implement without them. + +The major version was also the chance to revise some of the defaults and to align with proposals and specifications that come up in the meantime. + +So today (2020-10-10) webpack 5.0.0 is released, but this doesn't mean it's done, bugfree or even feature-complete. +As with webpack 4 we continue development by fixing problems and adding features. +In the next days there will probably be a lot bugfixes. Features will come later. + +## Common Questions + +### So what does the release mean? + +It means we finished doing breaking changes. +Many refactorings have been done to up-level the architecture and create a good base for future features (and current features). + +### So when is the time to upgrade? + +It depends. There is a good chance that upgrading fails and you would need to give it a second or 3rd try. +If you are open to that, try to upgrade now and provide feedback to webpack, plugins and loaders. +We are eager to fix those problems. Someone has to start and you would be one of the first ones benefiting from it. + +## Sponsoring Update + +Webpack is fully based upon [sponsoring](https://opencollective.com/webpack). +It's not tied to (and paid by) a big company like some other Open Source projects. +99% of the earnings from sponsoring are distributed towards contributors and maintainers based on the contributions they do. +We believe in investing the money towards making webpack better. + +But there is a pandemic, and companies ain't that much open to sponsoring anymore. +Webpack is suffering under these circumstances too (like many other companies and people). + +We were never able to pay our contributors the amount we think they deserve, but now we only have half of the money available, so we need to make a more serious cut. +Until the situation improves we will only pay contributors and maintainers the first 10 days of each month. +The remaining days they could work voluntarily, paid by their employer, work on something else, or take some days off. +This allows us to pay for their work in the first 10 days more equivalent to the invested time. + +The biggest "Thank You" goes to [trivago](https://tech.trivago.com/opensource) which has been sponsoring webpack a huge amount for the last 3 years. +Sadly they are unable to continue their sponsorship this year, as they have been hit hard by Covid-19. +I hope some other company steps up and follows these (gigantic) footsteps. + +Thanks to [all the sponsors](/#sponsors). + +## General direction + +This release focus on the following: + +- Improve build performance with Persistent Caching. +- Improve Long Term Caching with better algorithms and defaults. +- Improve bundle size with better Tree Shaking and Code Generation. +- Improve compatibility with the web platform. +- Clean up internal structures that were left in a weird state while implementing features in v4 without introducing any breaking changes. +- Prepare for future features by introducing breaking changes now, allowing us to stay on v5 for as long as possible. + +## **Migration** Guide + +[See here for a **migration** guide](/migrate/5) + +## Major Changes: Removals + +### Removed Deprecated Items + +All items deprecated in v4 were removed. + +**MIGRATION**: Make sure that your webpack 4 build does not print deprecation warnings. + +Here are a few things that were removed but did not have deprecation warnings in v4: + +- IgnorePlugin and BannerPlugin must now be passed only one argument that can be an object, string or function. + +### Deprecation codes + +New deprecations include a deprecation code so they are easier to reference. + +### Syntax deprecated + +`require.include` has been deprecated and will emit a warning by default when used. + +Behavior can be changed with `Rule.parser.requireInclude` to allowed, deprecated or disabled. + +### Automatic Node.js Polyfills Removed + +In the early days, webpack's aim was to allow running most Node.js modules in the browser, but the module landscape changed and many module uses are now written mainly for frontend purposes. Webpack <= 4 ships with polyfills for many of the Node.js core modules, which are automatically applied once a module uses any of the core modules (i.e. the `crypto` module). + +While this makes using modules written for Node.js easier, it adds these huge polyfills to the bundle. In many cases these polyfills are unnecessary. + +Webpack 5 stops automatically polyfilling these core modules and focus on frontend-compatible modules. Our goal is to improve compatibility with the web platform, where Node.js core modules are not available. + +**MIGRATION**: + +- Try to use frontend-compatible modules whenever possible. +- It's possible to manually add a polyfill for a Node.js core module. An error message will give a hint on how to achieve that. +- Package authors: Use the `browser` field in `package.json` to make a package frontend-compatible. Provide alternative implementations/dependencies for the browser. + +## Major Changes: Long Term Caching + +### Deterministic Chunk, Module IDs and Export names + +New algorithms were added for long term caching. These are enabled by default in production mode. + +`chunkIds: "deterministic"` +`moduleIds: "deterministic"` +`mangleExports: "deterministic"` + +The algorithms assign short (3 or 5 digits) numeric IDs to modules and chunks and short (2 characters) names to exports in a deterministic way. +This is a trade-off between bundle size and long term caching. + +`moduleIds/chunkIds/mangleExports: false` disables the default behavior and one can provide a custom algorithm via plugin. Note that in webpack 4 `moduleIds/chunkIds: false` without custom plugin resulted in a working build, while in webpack 5 you must provide a custom plugin. + +**MIGRATION**: Best use the default values for `chunkIds`, `moduleIds` and `mangleExports`. You can also opt-in to the old defaults `chunkIds: "size", moduleIds: "size", mangleExports: "size"`, this will generate smaller bundles, but invalidate them more often for caching. + +Note: In webpack 4 hashed module ids yielded reduced gzip performance. This was related to changed module order and has been fixed. + +Note: In webpack 5, `deterministic` Ids are enabled by default in production mode + +### Real Content Hash + +Webpack 5 will use a real hash of the file content when using `[contenthash]` now. Before it "only" used a hash of the internal structure. +This can be positive impact on long term caching when only comments are changed or variables are renamed. These changes are not visible after minimizing. + +## Major Changes: Development Support + +### Named Chunk IDs + +A new named chunk id algorithm enabled by default in development mode gives chunks (and filenames) human-readable names. +A Module ID is determined by its path, relative to the `context`. +A Chunk ID is determined by the chunk's content. + +So you no longer need to use `import(/* webpackChunkName: "name" */ "module")` for debugging. +But it would still make sense if you want to control the filenames for production environments. + +It's possible to use `chunkIds: "named"` in production, but make sure not to accidentally expose sensitive information about module names. + +**MIGRATION**: If you dislike the filenames being changed in development, you can pass `chunkIds: "natural"` to use the old numeric mode. + +### Module Federation + +Webpack 5 adds a new feature called "Module Federation", which allows multiple webpack builds to work together. +From runtime perspective modules from multiple builds will behave like a huge connected module graph. +From developer perspective modules can be imported from specified remote builds and used with minimal restrictions. + +For more details see [this separate guide](/concepts/module-federation). + +## Major Changes: New Web Platform Features + +### JSON modules + +JSON modules now align with the proposal and emit a warning when a non-default export is used. +JSON modules no longer have named exports when importing from a strict ECMAScript module. + +**MIGRATION**: Use the default export. + +Even when using the default export, unused properties are dropped by the `optimization.usedExports` optimization and properties are mangled by the `optimization.mangleExports` optimization. + +It's possible to specify a custom JSON parser in `Rule.parser.parse` to import JSON-like files (e.g. for toml, yaml, json5, etc.). + +### import.meta + +- `import.meta.webpackHot` is an alias for `module.hot` which is also available in strict ESM +- `import.meta.webpack` is the webpack major version as number +- `import.meta.url` is the `file:` url of the current file (similar to `__filename` but as file url) + +### Asset modules + +Webpack 5 has now native support for modules representing assets. +These modules will either emit a file into the output folder or inject a DataURI into the javascript bundle. +Either way they give a URL to work with. + +They can be used via multiple ways: + +- `import url from "./image.png"` and setting `type: "asset"` in `module.rules` when matching such import. (old way) +- `new URL("./image.png", import.meta.url)` (new way) + +The "new way" syntax was chosen to allow running code without bundler too. This syntax is also available in native ECMAScript modules in the browser. + +### Native Worker support + +When combining `new URL` for assets with `new Worker`/`new SharedWorker`/`navigator.serviceWorker.register` webpack will automatically create a new entrypoint for a web worker. + +`new Worker(new URL("./worker.js", import.meta.url))` + +The syntax was chosen to allow running code without bundler too. This syntax is also available in native ECMAScript modules in the browser. + +### URIs + +Webpack 5 supports handling of protocols in requests. + +- `data:` is supported. Base64 or raw encoding is supported. Mimetype can be mapped to loaders and module type in `module.rules`. Example: `import x from "data:text/javascript,export default 42"` +- `file:` is supported. +- `http(s):` is supported, but requires opt-in via `new webpack.experiments.schemesHttp(s)UriPlugin()` + - By default when targeting "web", these URIs result in requests to external resource (they are externals) + +Fragments in requests are supported: Example: `./file.js#fragment` + +### Async modules + +Webpack 5 supports so called "async modules". +That are modules that do not evaluate synchronously, but are async and Promise-based instead. + +Importing them via `import` is automatically handled and no additional syntax is needed and difference is hardly notice-able. + +Importing them via `require()` will return a Promise that resolves to the exports. + +In webpack there are multiple ways to have async modules: + +- async externals +- WebAssembly Modules in the new spec +- ECMAScript Modules that are using Top-Level-Await + +### Externals + +Webpack 5 adds additional external types to cover more applications: + +`promise`: An expression that evaluates to a Promise. The external module is an async module and the resolved value is used as module exports. + +`import`: Native `import()` is used to load the specified request. The external module is an async module. + +`module`: Not implemented yet, but planned to load modules via `import x from "..."`. + +`script`: Loads a url via ` + + + + +``` + +The same behaviors that apply to external ` + + + +``` + +A few behaviors are worth knowing about: + +- Multiple ` +``` + +The comment value is parsed with the same context the JS and CSS parsers use, so non-boolean values raise an `UnsupportedFeatureWarning`. + +## TypeScript Support (Experimental) + +W> **This feature is experimental and depends on a recent Node.js.** It requires Node.js 22.6 or later for the stable `module.stripTypeScriptTypes` API. The transform handles only erasable TypeScript syntax (see limitations below). For anything else, keep using `ts-loader` or `swc-loader`. + +Webpack 5.107 adds first-class TypeScript support behind a new `experiments.typescript` flag. With it enabled, webpack compiles `.ts`, `.cts`, and `.mts` files directly through Node.js's built-in [`module.stripTypeScriptTypes`](https://nodejs.org/api/module.html#modulestriptypescripttypescode-options), no external loader required. The flag is also turned on automatically by [`experiments.futureDefaults`](/configuration/experiments/#experimentsfuturedefaults). + +```js +// webpack.config.js +export default { + experiments: { + typescript: true, + }, + entry: './src/index.ts', +}; +``` + +Enabling the flag also wires up sensible defaults: rules for `.ts` / `.cts` / `.mts`, `.ts` added to extension resolution before `.js`, `extensionAlias` so `import "./foo.js"` also tries `./foo.ts`, `tsconfig.json` resolution, and the `"typescript"` conditional-exports key for monorepos that ship `.ts` sources. + +The transform only **erases types**: no type checking, no JSX / `.tsx`, and no non-erasable syntax (`enum`, `namespace`, parameter-property constructors, decorator metadata). These are the same constraints TypeScript enforces with [`erasableSyntaxOnly`](https://www.typescriptlang.org/tsconfig/#erasableSyntaxOnly). For type checking, pair the flag with `tsc --noEmit` or [`fork-ts-checker-webpack-plugin`](https://github.com/TypeStrong/fork-ts-checker-webpack-plugin). For JSX or non-erasable syntax, keep using `ts-loader` or `swc-loader`. + +See [`examples/typescript`](https://github.com/webpack/webpack/tree/main/examples/typescript) for the built-in setup and [`examples/typescript-non-erasable`](https://github.com/webpack/webpack/tree/main/examples/typescript-non-erasable) for the `ts-loader` fallback. + +## CSS Improvements + +### Scope Hoisting for CSS Modules + +Module concatenation (also known as scope hoisting) used to be a JavaScript-only optimization. Even with `experiments.css` enabled, CSS Modules pulled into a concatenated bundle still produced separate runtime instances. Starting with 5.107, the same optimization applies to CSS Modules whose export type is `text`, `css-style-sheet`, `style`, or `link`. The result is lower runtime overhead and smaller output in CSS-heavy bundles. + +```js +module.exports = { + experiments: { css: true }, + optimization: { + concatenateModules: true, + }, + module: { + rules: [ + { + test: /\.css$/, + type: 'css/module', + parser: { + exportType: 'css-style-sheet', + }, + }, + ], + }, +}; +``` + +### Pure Mode for CSS Modules + +A new `pure` parser option for `css/module` and `css/auto` mirrors the strict pure mode of `postcss-modules-local-by-default`. When enabled, every selector must contain at least one local class or id; otherwise webpack raises a build error. The point is to catch accidentally global selectors in CSS Modules before they make it into production. + +```js +module.exports = { + experiments: { css: true }, + module: { + parser: { + 'css/module': { + pure: true, + }, + }, + }, +}; +``` + +Two comments offer opt-outs when you need them. The first suppresses the check for a single rule: + +```css +/* cssmodules-pure-ignore */ +a { + /* suppressed only for this rule */ + color: blue; +} +``` + +The second, placed among the leading comments of a file before any rule, disables the check for the entire file: + +```css +/* cssmodules-pure-no-check */ +/* disables pure mode for this file */ + +a { + /* would normally fail under pure mode */ + color: red; +} +``` + +Nested rules inside a local-bearing ancestor count as pure-compliant, `&` resolves to the parent rule's purity, and `@keyframes` and `@counter-style` bodies are exempt. + +### `@value` in URLs and `@import` + +CSS Modules `@value` identifiers can now be used as the path argument to `@import` and inside `url()` references. This makes it easy to define shared paths and assets once and reuse them across stylesheets. + +```css +@value path: "./other.module.css"; +@import path; + +@value bg: "./image.png"; + +.a { + background: url(bg); +} +``` + +Both quoted (`"./x"`, `'./x'`) and bare (`./x`) forms of the value work. Whichever form you write is unwrapped and resolved as a module request, so the asset flows through the normal webpack resolver and asset pipeline instead of being left as a literal identifier. + +### Multiple Aliases via `exportsConvention` + +The function form of `generator.exportsConvention` for CSS Modules now accepts `string[]` in addition to `string`. Returning an array exports the local class under every name in the array, matching `css-loader`'s behavior. This is handy when you want to expose multiple aliases for a single class, for example both the original name and an uppercase version. + +```js +module.exports = { + experiments: { css: true }, + module: { + generator: { + 'css/module': { + exportsConvention: name => [name, name.toUpperCase()], + }, + }, + }, +}; +``` + +```js +// Usage in JS +import styles from './button.module.css'; + +console.log(styles.btn); // hashed class +console.log(styles.BTN); // same hashed class, uppercase alias +``` + +### `linkInsert` Hook + +If you've ever wanted to control where webpack inserts a stylesheet `` in the document, you now have a hook for it. `CssLoadingRuntimeModule.getCompilationHooks(compilation)` exposes a new `linkInsert` hook. It receives the default insertion source (`document.head.appendChild(link);`) and the chunk, and returns the JS used to attach the link. + +```js +const webpack = require('webpack'); + +class MyLinkInsertPlugin { + apply(compiler) { + compiler.hooks.thisCompilation.tap('MyLinkInsertPlugin', compilation => { + const hooks = + webpack.web.CssLoadingRuntimeModule.getCompilationHooks(compilation); + + // Override the default `document.head.appendChild(link);` + hooks.linkInsert.tap( + 'MyLinkInsertPlugin', + (source, chunk) => + 'link.setAttribute("data-injected", "true"); document.body.appendChild(link);' + ); + }); + } +} + +module.exports = { + experiments: { css: true }, + plugins: [new MyLinkInsertPlugin()], +}; +``` + +The hook is a `SyncWaterfallHook<[string, Chunk]>`. Return the default `source` to keep the original behavior, or return your own JS to override where (and how) the link is attached. + +### `orderModules` Hook + +When a chunk pulls in CSS from several files, webpack's default ordering is a topological sort of their import graph. That's the right call in most cases, but on real-world projects the import graph can be ambiguous enough to trigger the "Conflicting order between CSS …" warning, with no clean way out short of restructuring imports. + +A new `orderModules` hook on `CssModulesPlugin.getCompilationHooks(compilation)` gives plugin authors a deterministic escape hatch. It runs once per CSS source type (`CSS_IMPORT_TYPE` and `CSS_TYPE`) with the chunk's modules pre-sorted by full module name. Return an ordered `Module[]` to override the default, or return `undefined` to fall through to webpack's existing import-order topological sort. + +```js +const webpack = require('webpack'); + +class CssOrderByPathPlugin { + apply(compiler) { + compiler.hooks.thisCompilation.tap('CssOrderByPathPlugin', compilation => { + const hooks = + webpack.css.CssModulesPlugin.getCompilationHooks(compilation); + + // Modules arrive pre-sorted by full module name; return as-is for + // a deterministic file-path order that side-steps the conflicting + // order warning. + hooks.orderModules.tap( + 'CssOrderByPathPlugin', + (_chunk, modules) => modules + ); + }); + } +} + +module.exports = { + experiments: { css: true }, + plugins: [new CssOrderByPathPlugin()], +}; +``` + +The hook is a `SyncBailHook<[Chunk, Module[], Compilation], Module[] | undefined>`. The first tap to return a non-`undefined` value wins. + +## JavaScript and ESM + +### Anonymous Default Export Naming + +Webpack 5.106 introduced a fix-up that sets `.name` to `"default"` for anonymous default exports, matching the ES spec. It works correctly, but it injects unmangleable `Object.defineProperty` calls that inflate the bundle. For library consumers, who rarely rely on `.name === "default"`, that extra runtime helper is pure overhead. + +5.107 introduces a new option, `module.parser.javascript.anonymousDefaultExportName`, to control this behavior. It defaults to `true` for applications and `false` for libraries (when `output.library` is set). Apps stay spec-compliant by default; library authors stop paying for the extra runtime helper without having to know about it. + +```js +// input +export default function () { + /* ... */ +} + +// with `anonymousDefaultExportName: true` (default for apps) +// the runtime sets .name = "default" matching native ESM behavior +``` + +You can override the default explicitly: + +```js +module.exports = { + module: { + parser: { + javascript: { + anonymousDefaultExportName: false, + }, + }, + }, +}; +``` + +### Preserving `defer` and `source` Phase on Externals + +Webpack now preserves the `defer` and `source` import phase keywords on external dependencies in ESM output, the same way import attributes are already preserved. Previously, the phase keyword was stripped from the emitted statement, so an `import defer * as ns from "x"` against an external lost its deferred semantics in the output. + +For static `module` externals, namespace defer imports and single-default source imports are now emitted as native phase syntax at the top of the bundle: + +```js +// webpack.config.js +module.exports = { + output: { module: true }, + externalsType: 'module', + externals: { 'external-mod': 'external-mod' }, +}; +``` + +```text +// input +import defer * as ns from "external-mod"; +import source v from "external-mod"; + +// emitted output +import defer * as ns from "external-mod"; +import source v from "external-mod"; +``` + +For dynamic `import` externals, `import.defer("x")` and `import.source("x")` are emitted directly: + +```text +// input +const ns = await import.defer("external-mod"); +const src = await import.source("external-mod"); + +// emitted output +const ns = await import.defer("external-mod"); +const src = await import.source("external-mod"); +``` + +One related improvement: the same external imported with two different phases (or attribute sets) no longer collapses into a single `ExternalModule`. Each combination produces its own emit, so neither phase is silently dropped. + +### `#__NO_SIDE_EFFECTS__` Annotation + +Webpack now supports the [`#__NO_SIDE_EFFECTS__`](https://github.com/javascript-compiler-hints/compiler-notations-spec/blob/main/no-side-effects-notation-spec.md#2-const-variable-declaration) annotation to mark functions as pure for better tree shaking. Calls to functions annotated this way can be eliminated from the bundle when their return value is unused, even if the function body is not statically analyzable as pure. + +{/_ eslint-disable _/} + +```js +// utils.js +/*#__NO_SIDE_EFFECTS__*/ +export function createLogger(prefix) { + return msg => console.log(`[${prefix}] ${msg}`); +} + +export function realWork() { + // ... +} +``` + +{/_ eslint-enable _/} + +```js +// app.js +import { createLogger, realWork } from './utils'; + +// dropped, because `createLogger` is annotated and its result is unused +const unused = createLogger('debug'); + +realWork(); +``` + +W> The annotation currently only takes effect **within the module where it is declared**. Cross-module propagation is planned for a future PR. + +## Resolver Updates + +Webpack now adds `"module-sync"` to the default `conditionNames` for resolver defaults, aligning with Node.js. Node.js exposes the `module-sync` community condition for synchronously-loadable ESM, and this change affects the ESM, CJS, AMD, worker, wasm, and build-dependency resolvers. + +Concretely, the resolver defaults now include `module-sync` right before `module` in the condition chain: + +```js +// Before (5.106) +conditionNames: ['require', 'module', '...']; // CJS deps +conditionNames: ['import', 'module', '...']; // ESM deps + +// After (5.107) +conditionNames: ['require', 'module-sync', 'module', '...']; +conditionNames: ['import', 'module-sync', 'module', '...']; +``` + +This means packages that publish a `module-sync` export condition in their `package.json` will be picked up automatically without any additional configuration: + +```json +{ + "name": "my-package", + "exports": { + ".": { + "module-sync": "./esm/index.js", + "default": "./cjs/index.js" + } + } +} +``` + +## Bug Fixes + +Several bug fixes have been resolved since version [5.106](https://github.com/webpack/webpack/releases/tag/v5.106.0). Check the [changelog](https://github.com/webpack/webpack/blob/main/CHANGELOG.md) for all the details. + +## Thanks + +A big thank you to all our contributors and [sponsors](https://github.com/webpack/webpack?tab=readme-ov-file#sponsoring) +who made Webpack 5.107 possible. Your support, whether through code contributions, documentation, or financial sponsorship, helps keep Webpack evolving and improving for everyone. diff --git a/pages/blogs/index.md b/pages/blogs/index.md new file mode 100644 index 0000000..2e04cdd --- /dev/null +++ b/pages/blogs/index.md @@ -0,0 +1,13 @@ +--- +layout: blogs +title: Blog +sort: -1 +contributors: + - sokra +--- + +# Blog + +Read here for announcements. + +See recent blog posts in the side bar. diff --git a/pages/site.json b/pages/site.json index f405284..b3f8a4e 100644 --- a/pages/site.json +++ b/pages/site.json @@ -9,7 +9,7 @@ "text": "About" }, { - "link": "/blog", + "link": "/blogs", "text": "Blog" }, { @@ -84,7 +84,7 @@ }, { "text": "Blog", - "link": "/blog" + "link": "/blogs" }, { "text": "Support webpack", diff --git a/pages/site.mjs b/pages/site.mjs index 74f3a87..e62e787 100644 --- a/pages/site.mjs +++ b/pages/site.mjs @@ -1,6 +1,7 @@ import loaders from './docs/loaders/site.json' with { type: 'json' }; import plugins from './docs/plugins/site.json' with { type: 'json' }; import governance from './about/governance/site.json' with { type: 'json' }; +import blog from './blogs/site.json' with { type: 'json' }; import versions from '../versions.json' with { type: 'json' }; import { major } from 'semver'; @@ -37,4 +38,5 @@ export const sidebar = { ...loaders.sidebar, ...plugins.sidebar, ], + blogs: blog.sidebar, }; diff --git a/scripts/data/blogs.mjs b/scripts/data/blogs.mjs new file mode 100644 index 0000000..c3c54e4 --- /dev/null +++ b/scripts/data/blogs.mjs @@ -0,0 +1,118 @@ +// Builds the blog data file from local Markdown pages in the pages/blog directory. + +import { mkdir, writeFile, readdir, readFile } from 'node:fs/promises'; +import { dirname, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..', '..'); +const OUTPUT_DATA = join(ROOT, 'generated', 'blogs.json'); +const PAGES_DIR = join(ROOT, 'pages', 'blogs'); + +/** + * Extracts a short description from the Markdown content. + * + * @param {string} content + * @returns {string} + */ +const extractDescription = content => { + let bodyText = content.replace(/^---\n([\s\S]*?)\n---/, '').trim(); + bodyText = bodyText.replace(/^#+ .+/gm, ''); + return bodyText.substring(0, 150).trim() + '...'; +}; + +/** + * Reads local blog posts and generates the data files. + */ +const buildLocalBlogs = async () => { + const files = await readdir(PAGES_DIR); + const blogsData = []; + const fetchedPages = []; + + for (const fileName of files) { + if (!fileName.endsWith('.md') || fileName === 'index.md') continue; + + const filePath = join(PAGES_DIR, fileName); + const content = await readFile(filePath, 'utf8'); + const slug = fileName.replace(/\.md$/, ''); + + const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/); + if (!frontmatterMatch) continue; + + const yamlRaw = frontmatterMatch[1]; + + const titleMatch = yamlRaw.match(/title:\s*(.+)/); + const title = titleMatch + ? titleMatch[1].replace(/^[`'"]|[`'"]$/g, '').trim() + : ''; + + const cleanBody = content.slice(frontmatterMatch[0].length).trim(); + if (!/^# /m.test(cleanBody)) { + await writeFile( + filePath, + `${frontmatterMatch[0]}\n\n# ${title}\n\n${cleanBody}`, + 'utf8' + ); + } + const contributors = []; + const contributorsSection = yamlRaw.match( + /contributors:\n([\s\S]*?)(?=\n[a-z]|$)/i + ); + if (contributorsSection) { + const listMatches = contributorsSection[1].matchAll(/^\s*-\s*(.+)/gm); + for (const m of listMatches) { + contributors.push(m[1].trim()); + } + } + + // Extract date from the filename + const dateMatch = fileName.match(/^(\d{4}-\d{2}-\d{2})/); + const date = dateMatch ? dateMatch[1] : ''; + const description = extractDescription(content); + + blogsData.push({ + slug, + title, + contributors: contributors.length > 0 ? contributors : ['webpack'], + date, + description, + }); + + fetchedPages.push({ output: slug, label: title, date }); + console.log(`Processed local file: ${fileName}`); + } + + blogsData.sort((a, b) => new Date(b.date) - new Date(a.date)); + fetchedPages.sort((a, b) => new Date(b.date) - new Date(a.date)); + + return { blogsData, fetchedPages }; +}; + +const { blogsData, fetchedPages } = await buildLocalBlogs(); + +await mkdir(dirname(OUTPUT_DATA), { recursive: true }); +await writeFile(OUTPUT_DATA, `${JSON.stringify(blogsData, null, 2)}\n`, 'utf8'); +console.log(`[blogs] wrote ${OUTPUT_DATA} (${blogsData.length} posts)`); + +const siteJson = { + sidebar: [ + { + groupName: 'Blogs', + items: [ + { + link: '/blogs/index', + label: 'Overview', + }, + ...fetchedPages.map(({ output, label }) => ({ + link: `/blogs/${output}`, + label, + })), + ], + }, + ], +}; +await writeFile( + join(PAGES_DIR, 'site.json'), + JSON.stringify(siteJson, null, 2) + '\n', + 'utf8' +); +console.log(`Written: pages/blogs/site.json (${fetchedPages.length} pages)`);