diff --git a/.changeset/README.md b/.changeset/README.md new file mode 100644 index 00000000000..e5b6d8d6a67 --- /dev/null +++ b/.changeset/README.md @@ -0,0 +1,8 @@ +# Changesets + +Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works +with multi-package repos, or single-package repos to help you version and publish your code. You can +find the full documentation for it [in our repository](https://github.com/changesets/changesets) + +We have a quick list of common questions to get you started engaging with this project in +[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) diff --git a/.changeset/amd-async-external-type.md b/.changeset/amd-async-external-type.md new file mode 100644 index 00000000000..b8cab6efef4 --- /dev/null +++ b/.changeset/amd-async-external-type.md @@ -0,0 +1,5 @@ +--- +"webpack": minor +--- + +Add `amd-async` externals type loading AMD externals without an AMD library wrapper. diff --git a/.changeset/bump-webpack-sources-enhanced-resolve.md b/.changeset/bump-webpack-sources-enhanced-resolve.md new file mode 100644 index 00000000000..95d8ad5a956 --- /dev/null +++ b/.changeset/bump-webpack-sources-enhanced-resolve.md @@ -0,0 +1,5 @@ +--- +"webpack": patch +--- + +Update runtime dependencies; webpack-sources 3.5.1 and enhanced-resolve 5.24.2 cut peak memory. diff --git a/.changeset/changelog-generator.mjs b/.changeset/changelog-generator.mjs new file mode 100644 index 00000000000..6be4c933c64 --- /dev/null +++ b/.changeset/changelog-generator.mjs @@ -0,0 +1,167 @@ +import { getInfo, getInfoFromPullRequest } from "@changesets/get-github-info"; + +/** @typedef {import("@changesets/types").ChangelogFunctions} ChangelogFunctions */ + +/** + * @returns {{ GITHUB_SERVER_URL: string }} value + */ +function readEnv() { + const GITHUB_SERVER_URL = + process.env.GITHUB_SERVER_URL || "https://github.com"; + return { GITHUB_SERVER_URL }; +} + +// Retry GitHub API calls so a transient drop (e.g. GraphQL "Premature close") doesn't fail the release. +/** + * @template T + * @param {() => Promise} fn operation to retry + * @returns {Promise} result + */ +async function withRetry(fn) { + const maxAttempts = 5; + let lastError; + for (let attempt = 1; attempt <= maxAttempts; attempt++) { + try { + return await fn(); + } catch (err) { + lastError = err; + if (attempt === maxAttempts) break; + const delay = Math.min(1000 * 2 ** (attempt - 1), 8000); + await new Promise((resolve) => { + setTimeout(resolve, delay); + }); + } + } + throw lastError; +} + +/** @type {ChangelogFunctions} */ +const changelogFunctions = { + getDependencyReleaseLine: async ( + changesets, + dependenciesUpdated, + options + ) => { + if (!options.repo) { + throw new Error( + 'Please provide a repo to this changelog generator like this:\n"changelog": ["@changesets/changelog-github", { "repo": "org/repo" }]' + ); + } + if (dependenciesUpdated.length === 0) return ""; + + const changesetLink = `- Updated dependencies [${( + await Promise.all( + changesets.map(async (cs) => { + if (cs.commit) { + const { links } = await withRetry(() => + getInfo({ + repo: options.repo, + commit: cs.commit + }) + ); + return links.commit; + } + }) + ) + ) + .filter(Boolean) + .join(", ")}]:`; + + const updatedDependenciesList = dependenciesUpdated.map( + (dependency) => ` - ${dependency.name}@${dependency.newVersion}` + ); + + return [changesetLink, ...updatedDependenciesList].join("\n"); + }, + getReleaseLine: async (changeset, type, options) => { + const { GITHUB_SERVER_URL } = readEnv(); + if (!options || !options.repo) { + throw new Error( + 'Please provide a repo to this changelog generator like this:\n"changelog": ["@changesets/changelog-github", { "repo": "org/repo" }]' + ); + } + + /** @type {number | undefined} */ + let prFromSummary; + /** @type {string | undefined} */ + let commitFromSummary; + /** @type {string[]} */ + const usersFromSummary = []; + + const replacedChangelog = changeset.summary + .replace(/^\s*(?:pr|pull|pull\s+request):\s*#?(\d+)/im, (_, pr) => { + const num = Number(pr); + if (!Number.isNaN(num)) prFromSummary = num; + return ""; + }) + .replace(/^\s*commit:\s*([^\s]+)/im, (_, commit) => { + commitFromSummary = commit; + return ""; + }) + .replace(/^\s*(?:author|user):\s*@?([^\s]+)/gim, (_, user) => { + usersFromSummary.push(user); + return ""; + }) + .trim(); + + const [firstLine, ...futureLines] = replacedChangelog + .split("\n") + .map((l) => l.trimEnd()); + + const links = await (async () => { + if (prFromSummary !== undefined) { + let { links } = await withRetry(() => + getInfoFromPullRequest({ + repo: options.repo, + pull: prFromSummary + }) + ); + if (commitFromSummary) { + const shortCommitId = commitFromSummary.slice(0, 7); + links = { + ...links, + commit: `[\`${shortCommitId}\`](${GITHUB_SERVER_URL}/${options.repo}/commit/${commitFromSummary})` + }; + } + return links; + } + const commitToFetchFrom = commitFromSummary || changeset.commit; + if (commitToFetchFrom) { + const { links } = await withRetry(() => + getInfo({ + repo: options.repo, + commit: commitToFetchFrom + }) + ); + return links; + } + return { + commit: null, + pull: null, + user: null + }; + })(); + + const users = usersFromSummary.length + ? usersFromSummary + .map( + (userFromSummary) => + `[@${userFromSummary}](${GITHUB_SERVER_URL}/${userFromSummary})` + ) + .join(", ") + : links.user; + + let suffix = ""; + if (links.pull || links.commit || users) { + suffix = `(${users ? `by ${users} ` : ""}in ${ + links.pull || links.commit + })`; + } + + return `\n\n- ${firstLine} ${suffix}\n${futureLines + .map((l) => ` ${l}`) + .join("\n")}`; + } +}; + +export default changelogFunctions; diff --git a/.changeset/changeset-validate.mjs b/.changeset/changeset-validate.mjs new file mode 100644 index 00000000000..2e2073148c5 --- /dev/null +++ b/.changeset/changeset-validate.mjs @@ -0,0 +1,141 @@ +import fs from "fs/promises"; +import path from "path"; +import { fileURLToPath } from "url"; +import { simpleGit } from "simple-git"; +import pkgJson from "../package.json" with { type: "json" }; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const rootPath = path.join(__dirname, ".."); +const git = simpleGit(rootPath); + +const VALID_BUMPS = new Set(["major", "minor", "patch"]); +const FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/; +const ENTRY_RE = /^"([^"]+)"\s*:\s*([a-zA-Z]+)\s*$/; + +const toLines = (output) => + output + .split(/\r?\n/) + .map((line) => line.trim()) + .filter(Boolean); + +const isChangeset = (filePath) => { + const normalized = filePath.replace(/\\/g, "/"); + return ( + normalized.startsWith(".changeset/") && + normalized.endsWith(".md") && + normalized !== ".changeset/README.md" + ); +}; + +const gitDiff = async (more = []) => { + const args = [ + "diff", + "--name-only", + // cspell:ignore ACMR + "--diff-filter=ACMR", + ...more, + "--", + ".changeset/*.md" + ].filter(Boolean); + + return toLines(await git.raw(args)); +}; + +const getChangedFiles = async () => { + const files = new Set(); + const baseRef = process.env.GITHUB_BASE_REF; + + // GitHub Actions base diff + if (baseRef) { + for (const file of await gitDiff([`origin/${baseRef}...HEAD`])) { + if (isChangeset(file)) files.add(file); + } + } + // Local working tree changes + else { + const _files = [ + // Unstaged changes + ...(await gitDiff()), + // Staged but uncommitted changes + ...(await gitDiff(["--cached"])), + // Untracked files + ...(await git.status()).not_added + ]; + for (const file of _files) { + if (isChangeset(file)) files.add(file); + } + } + return files; +}; + +const validate = async (filePath) => { + const absoluteFilePath = path.join(rootPath, filePath); + const content = await fs.readFile(absoluteFilePath, "utf8"); + const frontmatterMatch = content.match(FRONTMATTER_RE); + const errors = []; + + if (!frontmatterMatch) { + errors.push("missing YAML frontmatter block"); + return errors; + } + + const entries = frontmatterMatch[1] + .split(/\r?\n/) + .map((line) => line.trim()) + .filter(Boolean); + + if (entries.length === 0) { + errors.push("frontmatter does not contain package bump entries"); + return errors; + } + + for (const entry of entries) { + const match = entry.match(ENTRY_RE); + if (!match) { + errors.push(`invalid frontmatter entry: ${entry}`); + continue; + } + + const [, pkgName, bumpType] = match; + if (pkgName !== pkgJson.name) { + errors.push( + `invalid package name "${pkgName}", expected "${pkgJson.name}"` + ); + } + + if (!VALID_BUMPS.has(bumpType)) { + errors.push( + `invalid bump type "${bumpType}", expected one of: major, minor, patch` + ); + } + } + + return errors; +}; + +const main = async () => { + const changedFiles = await getChangedFiles(); + + if (changedFiles.length === 0) { + console.log("No changed changeset files found."); + return; + } + + const failures = []; + for (const filePath of changedFiles) { + const errors = await validate(filePath); + for (const error of errors) { + failures.push(`${filePath}: ${error}`); + } + } + + if (failures.length > 0) { + console.error("Changeset validation failed:"); + for (const failure of failures) { + console.error(`- ${failure}`); + } + process.exitCode = 1; + } +}; + +main(); diff --git a/.changeset/config.json b/.changeset/config.json new file mode 100644 index 00000000000..5fcacb00918 --- /dev/null +++ b/.changeset/config.json @@ -0,0 +1,10 @@ +{ + "$schema": "https://unpkg.com/@changesets/config@3.1.2/schema.json", + "changelog": ["./changelog-generator.mjs", { "repo": "webpack/webpack" }], + "fixed": [], + "linked": [], + "access": "public", + "baseBranch": "main", + "updateInternalDependencies": "patch", + "ignore": [] +} diff --git a/.changeset/css-used-exports-shake.md b/.changeset/css-used-exports-shake.md new file mode 100644 index 00000000000..b996775da35 --- /dev/null +++ b/.changeset/css-used-exports-shake.md @@ -0,0 +1,5 @@ +--- +"webpack": patch +--- + +Fix unused CSS module exports in the JS wrapper. diff --git a/.changeset/html-parser-sources-html-entry-type.md b/.changeset/html-parser-sources-html-entry-type.md new file mode 100644 index 00000000000..fa3c704bc79 --- /dev/null +++ b/.changeset/html-parser-sources-html-entry-type.md @@ -0,0 +1,5 @@ +--- +"webpack": minor +--- + +Extend the HTML pipeline with new source types: an `html` link (a custom tag/attribute bundled as its own emitted page) and `rel="preload"`/`"prefetch"` links whose scripts/styles are bundled as chunks and rewritten to the built chunk URL. diff --git a/.changeset/js-parser-lazy-comments.md b/.changeset/js-parser-lazy-comments.md new file mode 100644 index 00000000000..2d727c6bfef --- /dev/null +++ b/.changeset/js-parser-lazy-comments.md @@ -0,0 +1,5 @@ +--- +"webpack": patch +--- + +Speed up JavaScript parsing with lazy comment text, template/regexp fast paths, linked scope maps and packed evaluated expressions. diff --git a/.changeset/js-parser-performance.md b/.changeset/js-parser-performance.md new file mode 100644 index 00000000000..b8087afaf33 --- /dev/null +++ b/.changeset/js-parser-performance.md @@ -0,0 +1,5 @@ +--- +"webpack": patch +--- + +Speed up JavaScript parsing and reduce parser memory usage. diff --git a/.changeset/missing-external-configuration-error.md b/.changeset/missing-external-configuration-error.md new file mode 100644 index 00000000000..baf4e8daadb --- /dev/null +++ b/.changeset/missing-external-configuration-error.md @@ -0,0 +1,5 @@ +--- +"webpack": patch +--- + +Emit an error when an object external has no entry for the used externals type. diff --git a/.changeset/module-library-entry-live-bindings.md b/.changeset/module-library-entry-live-bindings.md new file mode 100644 index 00000000000..0894d0aba6d --- /dev/null +++ b/.changeset/module-library-entry-live-bindings.md @@ -0,0 +1,5 @@ +--- +"webpack": patch +--- + +Keep ESM live bindings for a module library's entry exports instead of snapshotting them, including when the runtime is emitted as a separate chunk. diff --git a/.changeset/perf-codegen-template-context.md b/.changeset/perf-codegen-template-context.md new file mode 100644 index 00000000000..1e1acbebb01 --- /dev/null +++ b/.changeset/perf-codegen-template-context.md @@ -0,0 +1,5 @@ +--- +"webpack": patch +--- + +Reduce allocations in JS codegen, concatenation, queues and parser setup. diff --git a/.changeset/wasm-sync-mime-fallback.md b/.changeset/wasm-sync-mime-fallback.md new file mode 100644 index 00000000000..a9898bfb1da --- /dev/null +++ b/.changeset/wasm-sync-mime-fallback.md @@ -0,0 +1,5 @@ +--- +"webpack": minor +--- + +Add `output.wasmStreamingFallback` for wasm fallback on wrong MIME type. diff --git a/.editorconfig b/.editorconfig index e3fc128fcf4..d725dd33227 100644 --- a/.editorconfig +++ b/.editorconfig @@ -2,22 +2,33 @@ root = true [*] indent_style = tab -indent_size = 4 +indent_size = 2 charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true -max_line_length = 233 +max_line_length = 80 -[*.json] +[*.{yml,yaml,json}] indent_style = space indent_size = 2 -[*.yml] -indent_style = space -indent_size = 2 +[*.md] +trim_trailing_whitespace = false + +[*.snap] +trim_trailing_whitespace = false [test/cases/parsing/bom/bomfile.{css,js}] charset = utf-8-bom -[*.md] -trim_trailing_whitespace = false +[test/configCases/asset-modules/bytes/file.text] +insert_final_newline = false + +[test/configCases/asset-modules/bytes/file.svg] +insert_final_newline = false + +[test/configCases/asset-modules/concatenation/file.text] +insert_final_newline = false + +[test/configCases/css/no-extra-runtime-in-js/source.text] +insert_final_newline = false diff --git a/.eslintrc b/.eslintrc deleted file mode 100644 index 042af66f3ee..00000000000 --- a/.eslintrc +++ /dev/null @@ -1,35 +0,0 @@ -{ - "root": true, - "extends": [ - "eslint:recommended" - ], - "env": { - "node": true - }, - "rules": { - "strict": 0, - "camelcase": 0, - "curly": 0, - "indent": [2, "tab", { "SwitchCase": 1 }], - "eol-last": 1, - "no-shadow": 0, - "no-redeclare": 2, - "no-extra-bind": 1, - "no-empty": 0, - "no-process-exit": 1, - "no-underscore-dangle": 0, - "no-use-before-define": 0, - "no-undef": 2, - "no-unused-vars": 0, - "consistent-return": 0, - "no-inner-declarations": 1, - "no-loop-func": 1, - "space-before-function-paren": [2, "never"], - "space-before-blocks": [2, "always"], - "space-before-keywords": [2, "always"], - "no-console": 0, - "comma-dangle": 0, - "no-unexpected-multiline": 2, - "valid-jsdoc": 2 - } -} diff --git a/.gitattributes b/.gitattributes index ac579eb7bc0..0336cb2fb13 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,4 +1,25 @@ * text=auto -test/statsCases/* eol=lf +test/statsCases/** eol=lf +test/hotCases/** eol=lf +test/configCases/css/public-path/*.js eol=lf +test/configCases/defer-import/** eol=lf +test/configCases/html/sources/** eol=lf +test/configCases/html/link/** eol=lf +test/configCases/html/modulepreload-esm/** eol=lf +test/configCases/html/inline-script/** eol=lf +test/configCases/html/inline-script-classic/** eol=lf +test/configCases/html/script-src/** eol=lf +test/configCases/html/script-src-classic/** eol=lf +test/configCases/html/script-src-mixed/** eol=lf +test/configCases/html/style-tag/** eol=lf +test/configCases/html/style-tag-context/** eol=lf +test/configCases/html/style-tag-no-css/** eol=lf +test/configCases/html/svg-paint-server-references/** eol=lf +test/configCases/html/svg-presentation-url/** eol=lf +test/configCases/html/legacy-background/** eol=lf +test/configCases/html/legacy-and-obsolete-sources/** eol=lf examples/* eol=lf -bin/* eol=lf \ No newline at end of file +bin/* eol=lf +*.svg eol=lf +*.css eol=lf +**/*webpack.lock.data/** -text diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md deleted file mode 100644 index d2bfca66699..00000000000 --- a/.github/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,15 +0,0 @@ - - -**Do you want to request a *feature* or report a *bug*?** - - -**What is the current behavior?** - -**If the current behavior is a bug, please provide the steps to reproduce.** - - -**What is the expected behavior?** - -**If this is a feature request, what is motivation or use case for changing the behavior?** - -**Please mention other relevant information such as the browser version, Node.js version, Operating System and programming language.** diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md deleted file mode 100644 index 7af602ab91a..00000000000 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ /dev/null @@ -1,24 +0,0 @@ - - -**What kind of change does this PR introduce?** - - - -**Did you add tests for your changes?** - - - -**If relevant, link to documentation update:** - - - -**Summary** - - - - -**Does this PR introduce a breaking change?** - - - -**Other information** diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000000..93d3d2018ae --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,47 @@ +version: 2 +updates: + - package-ecosystem: npm + directory: "/" + schedule: + interval: "weekly" + open-pull-requests-limit: 20 + labels: + - dependencies + versioning-strategy: widen + groups: + dependencies: + patterns: + - "*" + update-types: + - "minor" + - "patch" + dependencies-major: + patterns: + - "*" + update-types: + - "major" + exclude-patterns: + - "eslint-scope" + - "rimraf" + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + open-pull-requests-limit: 20 + labels: + - dependencies + groups: + dependencies: + patterns: + - "*" + - package-ecosystem: "gitsubmodule" + directory: "/" + schedule: + interval: "weekly" + open-pull-requests-limit: 20 + labels: + - dependencies + groups: + dependencies: + patterns: + - "*" diff --git a/.github/scripts/publish-to-pkg-pr-new.mjs b/.github/scripts/publish-to-pkg-pr-new.mjs new file mode 100644 index 00000000000..f84aae84a6e --- /dev/null +++ b/.github/scripts/publish-to-pkg-pr-new.mjs @@ -0,0 +1,129 @@ +/* eslint-disable no-console */ +/* eslint-disable camelcase */ + +import fs from "fs"; + +/** + * @param {{ github: EXPECTED_ANY, context: EXPECTED_ANY }} params params + */ +export async function run({ github, context }) { + const output = JSON.parse(fs.readFileSync("output.json", "utf8")); + + const sha = + context.eventName === "pull_request" + ? context.payload.pull_request.head.sha + : context.sha; + + const commitUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/commit/${sha}`; + + const botCommentIdentifier = ""; + const body = `${botCommentIdentifier} +This PR is packaged and the instant preview is available (${commitUrl}). + +Install it locally: + +- npm + +\`\`\`shell +npm i -D webpack@${output.packages.map((p) => p.url).join(" ")} +\`\`\` + +- yarn + +\`\`\`shell +yarn add -D webpack@${output.packages.map((p) => p.url).join(" ")} +\`\`\` + +- pnpm + +\`\`\`shell +pnpm add -D webpack@${output.packages.map((p) => p.url).join(" ")} +\`\`\` +`; + + /** + * @param {number=} issueNumber PR number + * @returns {Promise} comments + */ + async function findBotComment(issueNumber) { + if (!issueNumber) return null; + const comments = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issueNumber + }); + return comments.data.find( + (comment) => + // Prevent unintentional overwriting + comment.user && + comment.user.login === "github-actions[bot]" && + comment.body.includes(botCommentIdentifier) + ); + } + + /** + * @param {number=} issueNumber issue number + * @returns {Promise} + */ + async function createOrUpdateComment(issueNumber) { + if (!issueNumber) { + console.log("No issue number provided. Cannot post or update comment."); + return; + } + + const existingComment = await findBotComment(issueNumber); + + if (existingComment) { + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: existingComment.id, + body + }); + } else { + await github.rest.issues.createComment({ + issue_number: issueNumber, + owner: context.repo.owner, + repo: context.repo.repo, + body + }); + } + } + + /** + * @returns {void} + */ + function logPublishInfo() { + console.log(`\n${"=".repeat(50)}`); + console.log("Publish Information"); + console.log("=".repeat(50)); + console.log("\nPublished Packages:"); + console.log(output.packages); + console.log("\nTemplates:"); + console.log(output.templates); + console.log(`\nCommit URL: ${commitUrl}`); + console.log(`\n${"=".repeat(50)}`); + } + + if (context.eventName === "pull_request") { + if (context.issue.number) { + await createOrUpdateComment(context.issue.number); + } + } else if (context.eventName === "push") { + const { data: prs } = + await github.rest.repos.listPullRequestsAssociatedWithCommit({ + owner: context.repo.owner, + repo: context.repo.repo, + commit_sha: sha + }); + + if (prs.length > 0) { + await createOrUpdateComment(prs[0].number); + } else { + console.log( + "No open pull request found for this push. Logging publish information to console:" + ); + logPublishInfo(); + } + } +} diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml new file mode 100644 index 00000000000..82b67c97b52 --- /dev/null +++ b/.github/workflows/benchmarks.yml @@ -0,0 +1,103 @@ +name: Benchmarks + +on: + push: + branches: [main] + pull_request: + branches: [main] + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + id-token: write # Required for OIDC authentication with CodSpeed + +jobs: + benchmark: + strategy: + fail-fast: false + matrix: + shard: [1/4, 2/4, 3/4, 4/4] + runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + fetch-tags: true + fetch-depth: 0 + + - name: Use Node.js + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: lts/* + cache: yarn + + - run: yarn --frozen-lockfile + + - run: yarn link --frozen-lockfile || true + + - run: yarn link webpack --frozen-lockfile + + - name: Check Memory Status + run: > + node -e 'const os=require("os"); + const toGb=b=>(b/1024**3).toFixed(2)+" GB"; + console.log(`--- Memory Status ---\nTotal: ${toGb(os.totalmem())}\nFree: ${toGb(os.freemem())}\nUsed: ${toGb(os.totalmem()-os.freemem())}`)' + + - name: Run benchmarks + uses: CodSpeedHQ/action@a4a36bb07c0638b0b4ca52bf1f3dad1b4289e52f # v4.18.1 + with: + run: yarn benchmark --ci + mode: "simulation" + env: + LAST_COMMIT: 1 + NEGATIVE_FILTER: on-schedule + SHARD: ${{ matrix.shard }} + benchmark-memory: + strategy: + fail-fast: false + matrix: + shard: [1/4, 2/4, 3/4, 4/4] + runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + fetch-tags: true + fetch-depth: 0 + + - name: Use Node.js + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: lts/* + cache: yarn + + - run: yarn --frozen-lockfile + + - run: yarn link --frozen-lockfile || true + + - run: yarn link webpack --frozen-lockfile + + - name: Check Memory Status + run: > + node -e 'const os=require("os"); + const toGb=b=>(b/1024**3).toFixed(2)+" GB"; + console.log(`--- Memory Status ---\nTotal: ${toGb(os.totalmem())}\nFree: ${toGb(os.freemem())}\nUsed: ${toGb(os.totalmem()-os.freemem())}`)' + + - name: Run memory benchmarks + uses: CodSpeedHQ/action@a4a36bb07c0638b0b4ca52bf1f3dad1b4289e52f # v4.18.1 + with: + run: yarn benchmark --ci + mode: "memory" + token: ${{ secrets.CODSPEED_TOKEN }} + env: + LAST_COMMIT: 1 + NEGATIVE_FILTER: on-schedule + SHARD: ${{ matrix.shard }} diff --git a/.github/workflows/dependabot.yml b/.github/workflows/dependabot.yml new file mode 100644 index 00000000000..2b7803c8d63 --- /dev/null +++ b/.github/workflows/dependabot.yml @@ -0,0 +1,38 @@ +name: Dependabot + +on: pull_request + +permissions: + contents: write + pull-requests: write + +jobs: + dependabot-auto-merge: + runs-on: ubuntu-latest + if: github.actor == 'dependabot[bot]' + steps: + - name: Generate Token + uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 + id: app-token + with: + app-id: ${{ secrets.BOT_APP_ID }} + private-key: ${{ secrets.BOT_PRIVATE_KEY }} + + - name: Dependabot metadata + id: dependabot-metadata + uses: dependabot/fetch-metadata@25dd0e34f4fe68f24cc83900b1fe3fe149efef98 # v3.1.0 + with: + github-token: "${{ steps.app-token.outputs.token }}" + + - name: Enable auto-merge for Dependabot PRs + if: steps.dependabot-metadata.outputs.update-type != 'version-update:semver-major' + run: | + if [ "$(gh pr status --json reviewDecision -q .currentBranch.reviewDecision)" != "APPROVED" ]; + then gh pr review --approve "$PR_URL" + else echo "PR already approved, skipping additional approvals to minimize emails/notification noise."; + fi + + gh pr merge --auto --squash "$PR_URL" + env: + PR_URL: ${{ github.event.pull_request.html_url }} + GH_TOKEN: ${{ steps.app-token.outputs.token }} diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml new file mode 100644 index 00000000000..fc41b6a2038 --- /dev/null +++ b/.github/workflows/dependency-review.yml @@ -0,0 +1,77 @@ +name: Dependency Review + +on: + pull_request: + +permissions: + contents: read + +jobs: + dependency-review: + runs-on: ubuntu-latest + steps: + - name: Checkout Repository + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + + - name: Dependency Review + uses: actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # v5.0.0 + with: + allow-dependencies-licenses: | + pkg:npm/@cspell/dict-django, + pkg:npm/@cspell/dict-en-common-misspellings, + pkg:npm/flatted, + pkg:npm/parse-imports, + pkg:npm/prettier, + pkg:npm/type-fest, + pkg:npm/abbrev, + pkg:npm/@pkgjs/parseargs, + pkg:npm/@apidevtools/json-schema-ref-parser, + pkg:npm/cookie-signature, + pkg:npm/ansis, + pkg:npm/strtok3, + pkg:npm/@sinclair/typebox, + pkg:npm/fs-monkey + allow-licenses: | + 0BSD, + AFL-1.1, + AFL-1.2, + AFL-2.0, + AFL-2.1, + AFL-3.0, + AGPL-3.0-only, + AGPL-3.0-or-later, + Apache-1.1, + Apache-2.0, + APSL-2.0, + Artistic-2.0, + BlueOak-1.0.0, + BSD-2-Clause, + BSD-3-Clause-Clear, + BSD-3-Clause, + BSL-1.0, + CAL-1.0, + CC-BY-3.0, + CC-BY-4.0, + CC-BY-SA-4.0, + CDDL-1.0, + CC0-1.0, + EPL-2.0, + GPL-2.0-only, + GPL-2.0-or-later, + GPL-2.0, + GPL-3.0-or-later, + ISC, + LGPL-2.0-only, + LGPL-2.1-only, + LGPL-2.1-or-later, + LGPL-2.1, + LGPL-3.0-only, + LGPL-3.0, + MIT, + MPL-2.0, + OFL-1.1, + PSF-2.0, + Python-2.0, + Python-2.0.1, + Unicode-DFS-2016, + Unlicense diff --git a/.github/workflows/examples.yml b/.github/workflows/examples.yml new file mode 100644 index 00000000000..486793d6a6a --- /dev/null +++ b/.github/workflows/examples.yml @@ -0,0 +1,48 @@ +name: Update examples + +on: + workflow_dispatch: + schedule: + - cron: "0 0 * * 0" + +jobs: + examples: + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + fetch-tags: true + fetch-depth: 0 + + - name: Use Node.js + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: lts/* + cache: yarn + + - run: yarn --frozen-lockfile + + - run: yarn link --frozen-lockfile || true + + - run: yarn link webpack --frozen-lockfile + + - run: yarn build:examples + + - name: Create Pull Request + uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + delete-branch: true + commit-message: | + docs: update examples + title: | + docs: update examples + body: | + Update examples. + + This PR was autogenerated. + branch: update-examples diff --git a/.github/workflows/pr-quality.yml b/.github/workflows/pr-quality.yml new file mode 100644 index 00000000000..5fc6579b8f2 --- /dev/null +++ b/.github/workflows/pr-quality.yml @@ -0,0 +1,14 @@ +name: PR Quality + +permissions: + contents: read + issues: read + pull-requests: write + +on: + pull_request_target: + types: [opened, reopened] + +jobs: + anti-slop: + uses: webpack/.github/.github/workflows/pr-quality.yml@a03552c758d8c244b3cfc2985aff7020469e0473 diff --git a/.github/workflows/publish-to-pkg-pr-new.yml b/.github/workflows/publish-to-pkg-pr-new.yml new file mode 100644 index 00000000000..cc6c6e20788 --- /dev/null +++ b/.github/workflows/publish-to-pkg-pr-new.yml @@ -0,0 +1,41 @@ +name: Publish to pkg.pr.new + +on: + pull_request: + branches: [main] + push: + branches: [main] + tags: ["!**"] + +permissions: + issues: write + pull-requests: write + +jobs: + publish: + if: | + github.repository == 'webpack/webpack' && + (github.event_name != 'pull_request' || + github.event.pull_request.head.repo.full_name == github.repository) + name: Publish to pkg.pr.new + runs-on: ubuntu-latest + outputs: + sha: ${{ steps.publish.outputs.sha }} + urls: ${{ steps.publish.outputs.urls }} + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - run: corepack enable + - name: Use Node.js + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: lts/* + cache: yarn + - run: yarn --frozen-lockfile + - run: npx pkg-pr-new publish --compact --json output.json --comment=off + - name: Add metadata to output + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { run } = await import('${{ github.workspace }}/.github/scripts/publish-to-pkg-pr-new.mjs'); + await run({ github, context }); diff --git a/.github/workflows/release-announcement.yml b/.github/workflows/release-announcement.yml new file mode 100644 index 00000000000..9d35b64f3e0 --- /dev/null +++ b/.github/workflows/release-announcement.yml @@ -0,0 +1,57 @@ +name: Release Announcement + +on: + release: + types: [published] + workflow_dispatch: + workflow_call: + +permissions: + contents: read + +jobs: + github-releases-to-discord: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + + - name: GitHub Releases to Discord + shell: bash + run: | + RELEASE_JSON=$(gh release view --json body,tagName,url,name) + + RAW_NAME=$(echo "$RELEASE_JSON" | jq -r '.name // .tagName // ""') + RAW_BODY=$(echo "$RELEASE_JSON" | jq -r '.body // ""') + HTML_URL=$(echo "$RELEASE_JSON" | jq -r '.url // ""') + RAW_AVATAR_URL="https://github.com/webpack/media/blob/90b54d02fa1cfc8aa864a8322202f74ac000f5d2/logo/icon.png" + + PAYLOAD=$(jq -n \ + --arg name "$RAW_NAME" \ + --arg avatar_url "$RAW_AVATAR_URL" \ + --arg url "$HTML_URL" \ + --arg body "$RAW_BODY" \ + --arg color "2105893" \ + --arg footer_text "Changelog" \ + --arg timestamp "$(date -u +'%Y-%m-%dT%H:%M:%SZ')" \ + '{ + username: "webpack Release Changelog", + avatar_url: $avatar_url, + content: "||<@&1450591255485743204>||", + embeds: [{ + title: ($name | .[0:256]), + url: $url, + color: ($color | tonumber), + description: ($body | .[0:4096]), + footer: { + text: ($footer_text | .[0:2048]) + }, + timestamp: $timestamp + }] + }') + + curl -X POST -H "Content-Type: application/json" \ + -d "$PAYLOAD" \ + "${DISCORD_WEBHOOK}" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000000..479a52bd85d --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,81 @@ +name: Release + +on: + push: + branches: + - main + +concurrency: ${{ github.workflow }}-${{ github.ref }} + +permissions: + id-token: write # Required for OIDC + contents: write + pull-requests: write + +jobs: + release: + if: github.repository == 'webpack/webpack' + name: Release + runs-on: ubuntu-latest + outputs: + published: ${{ steps.changesets.outputs.published }} + publishedPackages: ${{ steps.changesets.outputs.publishedPackages }} + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + + - name: Use Node.js + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: lts/* + cache: yarn + + - run: yarn --frozen-lockfile + + - name: Create Release Pull Request or Publish to npm + id: changesets + uses: changesets/action@a45c4d594aa4e2c509dc14a9f2b3b67ba3780d0d # v1.9.0 + with: + publish: node ./node_modules/.bin/changeset publish + commit: "chore(release): new release" + title: "chore(release): new release" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NPM_TOKEN: "" # https://github.com/changesets/changesets/issues/1152#issuecomment-3190884868 + + announce-release: + needs: release + if: needs.release.outputs.published == 'true' + uses: ./.github/workflows/release-announcement.yml + secrets: inherit + + trigger-documentation-update: + needs: release + if: needs.release.outputs.published == 'true' + runs-on: ubuntu-latest + steps: + - name: Generate Token + uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 + id: app-token + with: + app-id: ${{ secrets.BOT_APP_ID }} + private-key: ${{ secrets.BOT_PRIVATE_KEY }} + - name: Dispatch workflow + uses: actions/github-script@d746ffe35508b1917358783b479e04febd2b8f71 # v9.0.0 + env: + PUBLISHED_PACKAGES: ${{ needs.release.outputs.publishedPackages }} + with: + github-token: ${{ steps.app-token.outputs.token }} + script: | + const packages = JSON.parse(process.env.PUBLISHED_PACKAGES); + const pkg = packages.find(p => p.name === "webpack"); + if (!pkg) throw new Error("webpack package not found in published packages"); + + await github.rest.actions.createWorkflowDispatch({ + owner: "webpack", + repo: "webpack-doc-kit", + workflow_id: "release.yml", + ref: "main", + inputs: { + tag: "v" + pkg.version, + }, + }); diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 00000000000..9cca6a85674 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,472 @@ +name: Github Actions + +on: + push: + branches: [main] + pull_request: + branches: [main] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + fetch-depth: 0 + + - name: Use Node.js + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: lts/* + cache: yarn + + - run: yarn --frozen-lockfile + + - name: Cache prettier result + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: ./node_modules/.cache/prettier/.prettier-cache + key: lint-prettier-${{ runner.os }}-node-${{ hashFiles('**/yarn.lock', '**/.prettierrc.js') }} + restore-keys: lint-prettier- + + - name: Cache eslint result + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: .eslintcache + key: lint-eslint-${{ runner.os }}-node-${{ hashFiles('**/yarn.lock', '**/eslint.config.mjs') }} + restore-keys: lint-eslint- + + - name: Cache cspell result + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: .cspellcache + key: lint-cspell-${{ runner.os }}-node-${{ hashFiles('**/yarn.lock', '**/cspell.json') }} + restore-keys: lint-cspell- + + - run: yarn lint + + - name: Validate types using old typescript version + run: | + yarn upgrade typescript@5.0 @types/node@20 + yarn --frozen-lockfile + yarn validate:types + + - name: Validate changeset format + if: github.event_name == 'pull_request' + run: | + yarn validate:changeset + + types-coverage: + if: github.repository == 'webpack/webpack' && github.event.pull_request.head.repo.full_name == github.repository + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + + - name: Use Node.js + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: lts/* + cache: yarn + + - run: yarn --frozen-lockfile + + - run: yarn link --frozen-lockfile || true + + - run: yarn link webpack --frozen-lockfile + + - name: Collect types coverage + run: yarn types:cover:report + + - uses: jwalton/gh-find-current-pr@f3d61b485d2801773f7a07b2aaa3306bd8f8e653 # v1.3.5 + id: findPr + + - uses: Nef10/lcov-reporter-action@797ec1c1e78e3a9a7890c1a104d58b9e05fcef76 # v0.3.0 + with: + pr-number: ${{ steps.findPr.outputs.number }} + lcov-file: ./coverage/lcov.info + title: Types Coverage + delete-old-comments: true + + validate-legacy-node: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + + - name: Use Node.js 10.x + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: 10.x + cache: yarn + + # Remove `devDependencies` from `package.json` to avoid `yarn install` compatibility error + - run: node -e "const content = require('./package.json');delete content.devDependencies;require('fs').writeFileSync('package.json', JSON.stringify(content, null, 2));" + + # `--ignore-engines`: some transitive deps (e.g. node-releases) declare + # `engines.node >= 18` but run fine on old Node; matches the other legacy jobs + - run: yarn install --production --frozen-lockfile --ignore-engines + + basic: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + + - name: Use Node.js + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: lts/* + cache: yarn + + - run: yarn --frozen-lockfile + + - run: yarn link --frozen-lockfile || true + + - run: yarn link webpack --frozen-lockfile + + - run: yarn test:basic --ci + + unit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + + - name: Use Node.js + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: lts/* + cache: yarn + + - run: yarn --frozen-lockfile + + - run: yarn link --frozen-lockfile || true + + - run: yarn link webpack --frozen-lockfile + + - name: Cache jest result + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: .jest-cache + key: jest-unit-${{ runner.os }}-${{ hashFiles('**/yarn.lock', '**/jest.config.js') }}-${{ github.sha }} + restore-keys: | + jest-unit-${{ runner.os }}-${{ hashFiles('**/yarn.lock', '**/jest.config.js') }}- + jest-unit-${{ runner.os }}- + + - run: yarn cover:unit --ci --cacheDirectory .jest-cache + + - name: Codecov + uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 + with: + flags: unit + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + + test262: + needs: basic + strategy: + fail-fast: false + matrix: + shard: [1/2, 2/2] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + submodules: true + + - name: Use Node.js + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + # Pinned; revert to "latest" once https://github.com/nodejs/node/issues/63715 is resolved + node-version: "26.2.0" + cache: yarn + + - run: yarn --frozen-lockfile + + - run: yarn link --frozen-lockfile || true + + - run: yarn link webpack --frozen-lockfile + + - name: Cache jest result + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: .jest-cache + key: jest-test262-${{ matrix.shard }}-${{ runner.os }}-${{ hashFiles('**/yarn.lock', '**/jest.config.js') }}-${{ github.sha }} + restore-keys: | + jest-test262-${{ matrix.shard }}-${{ runner.os }}-${{ hashFiles('**/yarn.lock', '**/jest.config.js') }}- + jest-test262-${{ matrix.shard }}-${{ runner.os }}- + + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + repository: tc39/test262 + path: test/js/test262 + ref: f2d59ea6e4b2f6f87de9f0d18a41d73782b6a9bc + + - run: yarn cover:test262 --ci --cacheDirectory .jest-cache + env: + SHARD: ${{ matrix.shard }} + + - name: Codecov + uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 + with: + flags: test262 + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + + html5lib: + needs: basic + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + submodules: true + + - name: Use Node.js + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: lts/* + cache: yarn + + - run: yarn --frozen-lockfile + + - run: yarn link --frozen-lockfile || true + + - run: yarn link webpack --frozen-lockfile + + - name: Cache jest result + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: .jest-cache + key: jest-html5lib-${{ runner.os }}-${{ hashFiles('**/yarn.lock', '**/jest.config.js') }}-${{ github.sha }} + restore-keys: | + jest-html5lib-${{ runner.os }}-${{ hashFiles('**/yarn.lock', '**/jest.config.js') }}- + jest-html5lib-${{ runner.os }}- + + - run: yarn cover:html5lib --ci --cacheDirectory .jest-cache + + - name: Codecov + uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 + with: + flags: html5lib + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + + css-parsing: + needs: basic + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + submodules: true + + - name: Use Node.js + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: lts/* + cache: yarn + + - run: yarn --frozen-lockfile + + - run: yarn link --frozen-lockfile || true + + - run: yarn link webpack --frozen-lockfile + + - name: Cache jest result + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: .jest-cache + key: jest-css-parsing-${{ runner.os }}-${{ hashFiles('**/yarn.lock', '**/jest.config.js') }}-${{ github.sha }} + restore-keys: | + jest-css-parsing-${{ runner.os }}-${{ hashFiles('**/yarn.lock', '**/jest.config.js') }}- + jest-css-parsing-${{ runner.os }}- + + - run: yarn cover:css-parsing --ci --cacheDirectory .jest-cache + + - name: Codecov + uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 + with: + flags: css-parsing + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + + # Run webpack's test suite under Deno and Bun (spec validation suites excluded). + runtimes: + needs: basic + strategy: + fail-fast: false + matrix: + runtime: [deno, bun] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + + - name: Use Node.js + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: lts/* + cache: yarn + + - name: Use Deno + if: matrix.runtime == 'deno' + uses: denoland/setup-deno@667a34cdef165d8d2b2e98dde39547c9daac7282 # v2.0.4 + with: + deno-version: v2.x + + - name: Use Bun + if: matrix.runtime == 'bun' + uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 + with: + bun-version: latest + + - run: yarn --frozen-lockfile + + - run: yarn link --frozen-lockfile || true + + - run: yarn link webpack --frozen-lockfile + + # Bun runs with worker_threads (see test:base:bun): in-band's single heap + # OOMs the runner on the full suite and Bun's child_process workers hang, but + # threads work once jest-worker loads the Bun preload per thread. Apply that + # patch here (Bun only) rather than via a repo-wide postinstall. + - if: matrix.runtime == 'bun' + run: git apply test/patches/jest-worker+30.4.1.patch + + # Deno and Bun both run with jest's parallel workers. The HotTestCases + # suites are dropped for Deno only (see test:deno): under Deno's event-loop + # timing webpack's async cache/compilation tails outlive a suite's teardown + # and require a module after the Jest environment is gone. Bun runs them with + # only a few per-case test.filter.js skips. + - run: yarn test:${{ matrix.runtime }} --ci + + integration: + needs: basic + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + node-version: [10.x, 22.x, 24.x, 26.x] + part: [a, b] + include: + # Test with main branches of webpack dependencies + - os: ubuntu-latest + node-version: lts/* + part: a + use_main_branches: 1 + - os: ubuntu-latest + node-version: lts/* + part: b + use_main_branches: 1 + # Test on old Node.js versions + - os: ubuntu-latest + node-version: 20.x + part: a + - os: ubuntu-latest + node-version: 18.x + part: a + - os: ubuntu-latest + node-version: 16.x + part: a + - os: ubuntu-latest + node-version: 14.x + part: a + - os: ubuntu-latest + node-version: 12.x + part: a + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + + - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + id: calculate_architecture + with: + result-encoding: string + script: | + if ('${{ matrix.os }}' === 'macos-latest' && '${{ matrix['node-version'] }}' === '10.x') { + return "x64" + } else { + return '' + } + + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: ${{ matrix.node-version }} + architecture: ${{ steps.calculate_architecture.outputs.result }} + cache: yarn + + # Install old `jest` version and deps for legacy node versions + - run: | + yarn upgrade jest@^27.5.0 jest-circus@^27.5.0 jest-cli@^27.5.0 jest-diff@^27.5.0 jest-environment-node@^27.5.0 jest-snapshot@^27.5.0 jest-junit@^13.0.0 @types/jest@^27.4.0 pretty-format@^27.0.2 husky@^8.0.3 lint-staged@^13.2.1 cspell@^6.31.1 open-cli@^7.2.0 coffee-loader@^1.0.0 babel-loader@^8.1.0 style-loader@^2.0.0 css-loader@^5.0.1 less-loader@^8.1.1 less@4.5.1 mini-css-extract-plugin@^1.6.1 nyc@^15.1.0 memfs@4.14.0 --ignore-engines + yarn --frozen-lockfile --ignore-engines + if: matrix.node-version == '10.x' || matrix.node-version == '12.x' || matrix.node-version == '14.x' + + - run: | + yarn upgrade jest@^27.5.0 jest-circus@^27.5.0 jest-cli@^27.5.0 jest-diff@^27.5.0 jest-environment-node@^27.5.0 jest-snapshot@^27.5.0 jest-junit@^13.0.0 @types/jest@^27.4.0 pretty-format@^27.0.2 husky@^8.0.3 lint-staged@^13.2.1 nyc@^15.1.0 coffee-loader@1.0.0 babel-loader@^8.1.0 style-loader@^2.0.0 css-loader@^5.0.1 less-loader@^8.1.1 mini-css-extract-plugin@^1.6.1 --ignore-engines + yarn --frozen-lockfile + if: matrix.node-version == '16.x' + + - run: | + yarn upgrade cspell@^8.8.4 lint-staged@^15.2.5 --ignore-engines + yarn --frozen-lockfile + if: matrix.node-version == '18.x' + + - run: | + yarn upgrade cspell@^9 --ignore-engines + yarn --frozen-lockfile + if: matrix.node-version == '20.x' + + - run: | + yarn upgrade pkg-pr-new@0.0.66 + yarn --frozen-lockfile + if: matrix.node-version == '22.x' + + # Install main version of our deps + - run: yarn upgrade enhanced-resolve@webpack/enhanced-resolve#main loader-runner@webpack/loader-runner#main webpack-sources@webpack/webpack-sources#main watchpack@webpack/watchpack#main tapable@webpack/tapable#main + if: matrix.use_main_branches == '1' + + # Install dependencies for LTS node versions + - run: yarn --frozen-lockfile + if: matrix.node-version != '10.x' && matrix.node-version != '12.x' && matrix.node-version != '14.x' && matrix.node-version != '16.x' && matrix.node-version != '18.x' && matrix.node-version != '20.x' && matrix.node-version != '22.x' + + - run: yarn link --frozen-lockfile || true + + - run: yarn link webpack --frozen-lockfile + + - name: Cache jest result + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: .jest-cache + key: jest-integration-${{ runner.os }}-node-${{ matrix.node-version }}-${{ matrix.part }}-${{ hashFiles('**/yarn.lock', '**/jest.config.js') }}-${{ github.sha }} + restore-keys: | + jest-integration-${{ runner.os }}-node-${{ matrix.node-version }}-${{ matrix.part }}-${{ hashFiles('**/yarn.lock', '**/jest.config.js') }}- + jest-integration-${{ runner.os }}-node-${{ matrix.node-version }}-${{ matrix.part }}- + + - run: yarn cover:integration:${{ matrix.part }} --ci --cacheDirectory .jest-cache || yarn cover:integration:${{ matrix.part }} --ci --cacheDirectory .jest-cache -f + env: + MAIN_BRANCHES: ${{ matrix.use_main_branches }} + if: matrix.node-version != '10.x' && matrix.node-version != '12.x' && matrix.node-version != '14.x' && matrix.node-version != '16.x' && matrix.node-version != '18.x' && matrix.node-version != '20.x' + + # Don't run code coverage analysis on older versions of NodeJS, this will speed up our CI + - run: yarn test:integration:${{ matrix.part }} --ci --cacheDirectory .jest-cache || yarn test:integration:${{ matrix.part }} --ci --cacheDirectory .jest-cache -f + if: matrix.node-version == '10.x' || matrix.node-version == '12.x' || matrix.node-version == '14.x' || matrix.node-version == '16.x' || matrix.node-version == '18.x' || matrix.node-version == '20.x' + + - run: yarn report:cover:merge + if: matrix.node-version != '10.x' && matrix.node-version != '12.x' && matrix.node-version != '14.x' && matrix.node-version != '16.x' && matrix.node-version != '18.x' && matrix.node-version != '20.x' + + - name: Codecov + uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 + with: + files: ./coverage/coverage-nyc.json,./coverage/coverage-final.json + directory: ./coverage/ + disable_search: true + flags: integration + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + if: matrix.node-version != '10.x' && matrix.node-version != '12.x' && matrix.node-version != '14.x' && matrix.node-version != '16.x' && matrix.node-version != '18.x' && matrix.node-version != '20.x' diff --git a/.gitignore b/.gitignore index 10f7b5c8ea9..a2042c75c70 100644 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,24 @@ /node_modules /test/js /test/browsertest/js -/benchmark/js -/benchmark/fixtures -/examples/*/js +/test/fixtures/temp-* +/test/temp +/test/ChangesAndRemovals +/test/ChangesAndRemovalsTemp +/test/**/dev-defaults.webpack.lock +/test/**/generated/** +/examples/**/dist +/examples/nodejs-addons/build/** +/assembly/**/*.wat +/assembly/**/*.wasm /coverage +/.nyc_output +/.jest-cache .DS_Store *.log .idea -yarn.lock .vscode +.cache +.eslintcache +.cspellcache +package-lock.json diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000000..27660ffe529 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,9 @@ +[submodule "test/test262-cases"] + path = test/test262-cases + url = https://github.com/tc39/test262 +[submodule "test/html5lib-tests"] + path = test/html5lib-tests + url = https://github.com/html5lib/html5lib-tests.git +[submodule "test/css-parsing-tests"] + path = test/css-parsing-tests + url = https://github.com/CourtBouillon/css-parsing-tests.git diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100755 index 00000000000..041c660c92b --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1 @@ +npx --no-install lint-staged diff --git a/.istanbul.yml b/.istanbul.yml new file mode 100644 index 00000000000..380ddeb6d88 --- /dev/null +++ b/.istanbul.yml @@ -0,0 +1,4 @@ +instrumentation: + excludes: + - "**/*.runtime.js" + - ".github/*" diff --git a/.jsbeautifyrc b/.jsbeautifyrc deleted file mode 100644 index 79b04984674..00000000000 --- a/.jsbeautifyrc +++ /dev/null @@ -1,25 +0,0 @@ -{ - "js": { - "allowed_file_extensions": ["js", "json", "jshintrc", "jsbeautifyrc"], - "brace_style": "collapse", - "break_chained_methods": false, - "e4x": true, - "eval_code": false, - "end_with_newline": true, - "indent_char": "\t", - "indent_level": 0, - "indent_size": 1, - "indent_with_tabs": true, - "jslint_happy": false, - "jslint_happy_align_switch_case": true, - "space_after_anon_function": false, - "keep_array_indentation": false, - "keep_function_indentation": false, - "max_preserve_newlines": 2, - "preserve_newlines": true, - "space_before_conditional": false, - "space_in_paren": false, - "unescape_strings": false, - "wrap_line_length": 0 - } -} \ No newline at end of file diff --git a/.npmrc b/.npmrc new file mode 100644 index 00000000000..9cf9495031e --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +package-lock=false \ No newline at end of file diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000000..b19d9bf01ba --- /dev/null +++ b/.prettierignore @@ -0,0 +1,51 @@ +package.json + +# Ignore some test files +test/**/*.* +!test/*.js +!test/*.cjs +!test/*.mjs +!test/**/webpack.config.js +!test/**/webpack.config.cjs +!test/**/webpack.config.mjs +!test/**/test.config.js +!test/**/test.filter.js +!test/**/errors.js +!test/**/warnings.js +!test/**/deprecations.js +!test/**/infrastructure-log.js +!test/*.md +!test/helpers/*.* +!test/harness/**/*.* +!test/benchmarkCases/**/*.mjs +test/js/**/*.* +test/test262-cases/**/*.* + +# Ignore some folders +benchmark/ +coverage/ + +# Ignore generated files +*.check.js +*.check.d.ts +types.d.ts +declarations/WebpackOptions.d.ts + +# Ignore precompiled schemas +schemas/**/*.check.js + +# Ignore example fixtures +examples/** +!examples/*/ +!examples/**/internals/ +!examples/**/internals/** +!examples/**/webpack.config.js +!examples/**/webpack.config.cjs +!examples/**/webpack.config.mjs +!examples/**/test.filter.js + +.vscode/**/*.* + +# Ignore local working files +pr.md +issue.md diff --git a/.prettierrc.js b/.prettierrc.js new file mode 100644 index 00000000000..04b93b8c6a7 --- /dev/null +++ b/.prettierrc.js @@ -0,0 +1,24 @@ +"use strict"; + +module.exports = { + printWidth: 80, + useTabs: true, + tabWidth: 2, + trailingComma: "none", + arrowParens: "always", + overrides: [ + { + files: "*.json", + options: { + parser: "json", + useTabs: false + } + }, + { + files: "*.{cts,mts,ts}", + options: { + parser: "typescript" + } + } + ] +}; diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 1e6a21aa2e4..00000000000 --- a/.travis.yml +++ /dev/null @@ -1,48 +0,0 @@ -sudo: false -language: node_js - -cache: - directories: - - $HOME/.npm - - $HOME/.yarn-cache - -matrix: - include: - - os: linux - node_js: "7" - env: NO_WATCH_TESTS=1 JOB_PART=lint - - os: linux - node_js: "7" - env: NO_WATCH_TESTS=1 JOB_PART=test - - os: linux - node_js: "6" - env: NO_WATCH_TESTS=1 JOB_PART=test - - os: linux - node_js: "4" - env: NO_WATCH_TESTS=1 JOB_PART=test - - os: linux - node_js: "v0.12.17" - env: NO_WATCH_TESTS=1 JOB_PART=test - - os: osx - node_js: "7" - env: NO_WATCH_TESTS=1 JOB_PART=test - - os: osx - node_js: "6" - env: NO_WATCH_TESTS=1 JOB_PART=test - - os: osx - node_js: "4" - env: NO_WATCH_TESTS=1 JOB_PART=test - allow_failures: - - node_js: "v0.12.17" - - os: osx - fast_finish: true - -script: npm run travis:$JOB_PART - -before_install: if [[ `npm -v` != 3* ]]; then npm i -g npm@3; fi -install: - - bash ./ci/travis-install.sh -after_success: - - cat ./coverage/lcov.info | node_modules/.bin/coveralls --verbose - - cat ./coverage/coverage.json | node_modules/codecov.io/bin/codecov.io.js - - rm -rf ./coverage diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000000..cc9ddc649fe --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,300 @@ +# Webpack Development Guide + +> Note: CLAUDE.md is a symlink to AGENTS.md. They are the same file. + +## Conventions in this guide + +A `> [!REQUIRED]` callout placed immediately under a heading marks that whole section as **mandatory and not optional**: follow it exactly, do not paraphrase, do not skip, do not substitute a similar-looking convention from other tooling. Reviewers have repeatedly flagged that REQUIRED sections (especially the [Pull request body](#pull-request-body)) are being skipped or partially filled in — doing so blocks the PR every time. Read each REQUIRED section in full whenever it applies; do not rely on memory or on a previous task's output. Sections without the callout are normal guidance — apply judgement. + +## Project Overview + +> [!REQUIRED] + +The directory listings below are the canonical map of the repository. **Whenever you add, rename, or remove a top-level directory** (under the repo root, under `lib/`, under `test/`, or under `schemas/`) you must update the matching bullet here in the same commit. CI does not check this — drift is only caught by humans, which is why it must be part of the change itself. If a new directory does not fit any existing group, add a new group rather than dropping the entry. + +webpack is a JavaScript module bundler. Package manager: **yarn**. + +**Source** + +- `lib/` — Main source code (CommonJS only; types declared via JSDoc `@typedef`). + - `lib/asset/` — Asset modules (images, fonts, raw files). + - `lib/async-modules/` — Top-level await. + - `lib/bun/` — Bun target externals preset (`bun:*` and node.js built-in modules). + - `lib/cache/` — Filesystem and memory caches. + - `lib/config/` — Config defaults, normalization, target presets. + - `lib/container/` — Module Federation. + - `lib/css/` — CSS Modules, CSS parsing and generation. + - `lib/debug/` — Debug helpers. + - `lib/dependencies/` — `Dependency` classes and their templates (HarmonyImport, CommonJsRequire, RequireContext, …). + - `lib/dll/` — DllPlugin / DllReferencePlugin. + - `lib/deno/`, `lib/electron/`, `lib/node/`, `lib/web/`, `lib/webworker/` — Target-specific runtime templates and externals presets. + - `lib/errors/` — Error class hierarchy. + - `lib/esm/` — ESM-specific output (e.g. `import.meta`). + - `lib/hmr/` — Hot Module Replacement plugins. + - `lib/html/` — Experimental HTML support. + - `lib/ids/` — Module/chunk id assignment plugins. + - `lib/javascript/` — JavaScript parsing (acorn), generation, exports analysis. + - `lib/json/` — JSON modules. + - `lib/library/` — UMD/AMD/ESM/CommonJS library output formats. + - `lib/logging/` — Logger API and console formatting. + - `lib/optimize/` — Optimization plugins (`SplitChunksPlugin`, `ConcatenatedModule`, …). + - `lib/performance/` — Asset/entrypoint size hints. + - `lib/prefetch/` — Prefetch/preload plugins. + - `lib/rules/` — `module.rules` matching engine. + - `lib/runtime/` — Runtime modules emitted into bundles (chunk loaders, public-path, …). + - `lib/schemes/` — Custom URL scheme handlers (`data:`, `http:`, …). + - `lib/serialization/` — Persistent cache serialization. + - `lib/sharing/` — Shared modules / Module Federation runtime. + - `lib/stats/` — Stats output (default printer, JSON factories). + - `lib/typescript/` — Experimental TypeScript module support (strip types via the Node.js TypeScript API). + - `lib/url/` — `new URL(asset, import.meta.url)` references. + - `lib/util/` — Utility helpers. + - `lib/wasm/`, `lib/wasm-async/`, `lib/wasm-sync/` — WebAssembly module support. +- `hot/` — Runtime code shipped to browsers for HMR (browser-side, not Node tooling). +- `bin/` — `webpack` CLI entry point. +- `tooling/` — Repo-internal build scripts (runtime/wasm code generators, hash-debug tool); invoked by `yarn fix:special`. +- `assembly/` — WebAssembly source for the hash function. +- `setup/` — One-time setup scripts. + +**Schemas (the source of truth for webpack's config API)** + +- `schemas/WebpackOptions.json` — top-level webpack options schema. +- `schemas/plugins/*.json` — per-plugin option schemas (`BannerPlugin`, `IgnorePlugin`, `ProgressPlugin`, `SourceMapDevToolPlugin`, …). +- `schemas/_container.json`, `schemas/_sharing.json` — Module Federation sub-schemas. + +**Tests** — see [TESTING_DOCS.md](TESTING_DOCS.md) for directory structure, naming, and how to run a single case. + +- `test/` — All test suites (`cases/`, `configCases/`, `watchCases/`, `hotCases/`, `statsCases/`, `typesCases/`, `test262-cases/`, `html5lib-tests/`, `css-parsing-tests/`, `benchmarkCases/`, `memoryLimitCases/`, etc.). + +**Examples & changesets** + +- `examples/` — Usage examples (build with `yarn build:examples`). +- `.changeset/` — Pending changeset files for the next release. + +**Auto-generated — do not edit by hand; regenerate via `yarn fix:special`** + +- `types.d.ts`, `declarations/**/*.d.ts`, `schemas/**/*.check.{js,d.ts}`, generated runtime code under `lib/`. + +**Hand-maintained type declarations (these _are_ editable)** + +- `declarations.d.ts`, `declarations.test.d.ts`, `module.d.ts`. + +**Configuration** + +- `package.json` — All commands (defined in `scripts`). +- `tsconfig*.json` — TypeScript configs (one per surface: `lib`, `hot`, types tests, validation, benchmarks). +- `eslint.config.mjs`, `cspell.json`, `jest.config.js`, `generate-types-config.js` — Lint/spell/test/type-gen configs. +- `.github/workflows/`, `.github/scripts/` — CI. +- `test/patches/` — test-only dependency patches (e.g. jest-worker) applied via `git apply` in the CI Bun test job. + +## Coding Standards + +### Source language: CommonJS + JSDoc + +`lib/` is CommonJS only. Use `module.exports` / `require()`, never `import`/`export` syntax. Types are declared via JSDoc — `@typedef {import("./Other")} Other` and friends — never TypeScript syntax inside `.js` files. The JSDoc annotations are compiled into `types.d.ts` by `yarn fix:special`. + +### Type annotations + +Prefer the most specific real type. `EXPECTED_ANY`, `EXPECTED_OBJECT`, and `EXPECTED_FUNCTION` (aliases for `any`, `object`, `Function`) are an escape hatch, not a default — use them **only** when the value genuinely can be any value, any object, or any function. When you simply don't know the type yet, reach for `unknown` and narrow it, rather than widening to `EXPECTED_ANY`. This applies in `test/` too: if a real type (e.g. an imported `import("…").Foo`) fits, use it instead of `EXPECTED_ANY`. + +Prefer a generic (`@template`) over a widened type whenever a function's output type depends on its input — it keeps callers precisely typed instead of collapsing to `EXPECTED_ANY`. + +### Source file headers + +Every source file under `lib/` (and `hot/`, `tooling/`) opens with the MIT license header. When adding a **new** file, set the `Author` line to its actual author (`Author @`) — don't copy another file's author line. + +### Code comments + +> [!REQUIRED] + +Comments inside `lib/`, `hot/`, `tooling/`, and `test/` must be **as short as possible** — ideally one line, at most two short lines. Every line must add information a careful reader can't get from the code itself: a hidden invariant, a non-obvious ordering constraint, a workaround, or the name of the higher-level concept the block implements. **Never** write multi-paragraph essays, restate what the next line obviously does, narrate the diff, restate the PR description, or quote the user/task framing. + +JSDoc on exported symbols stays as-is — that's the type contract, not commentary. + +## Performance and memory + +webpack is a bundler — users measure it by build time and peak heap usage. Many changes in `lib/` end up on per-module hot paths (sometimes per module × runtime, or per chunk × module) on user builds, so constant factors compound. Always weigh the time and memory cost of a change, including bug fixes and refactors: less allocation, smaller `Map`/`Set` footprints, and fewer closures retained on hot paths are wins worth pursuing — less is better. When introducing or holding any per-`Compilation` state, ask whether it can be released after seal/emit so large compilation data structures are not retained longer than necessary. See #15521 for an example of how this class of memory issue can surface. + +## Auto-generated files + +> [!REQUIRED] + +These files are produced by `yarn fix:special` and must not be edited by hand: + +- `types.d.ts` — compiled from JSDoc + schemas. +- `declarations/**/*.d.ts` — per-schema/plugin declarations emitted from `schemas/**/*.json`. +- `schemas/**/*.check.{js,d.ts}` — precompiled schema validators. +- Generated runtime code under `lib/` (driven by `tooling/generate-runtime-code.js`). + +The hand-maintained type declarations (`declarations.d.ts`, `declarations.test.d.ts`, `module.d.ts`) _are_ editable. + +Re-run `yarn fix:special` **before the next commit** whenever you touch: + +- `schemas/**/*.json` — reshapes validators, declarations, and `types.d.ts`. +- `lib/**/*.js` JSDoc on anything reachable from a public export — regenerates `types.d.ts`. +- `tooling/generate-runtime-code.js`, `tooling/generate-wasm-code.js`, or any file they consume. + +CI's `lint` job verifies these outputs are up to date. The combined `yarn fix` script runs `fix:code` + `fix:special` + `fmt` in one go; prefer it as the final step. + +## Development Workflow + +### 1. Making Changes + +Modify source code in `lib/` as needed. + +**Adding or renaming a webpack option** requires edits in every layer, in this order: + +1. **Schema** — `schemas/WebpackOptions.json` (or `schemas/plugins/.json`). +2. **Defaults** — `lib/config/defaults.js`. +3. **Normalization** — `lib/config/normalization.js`. +4. **Implementation** — the site that consumes the option. + +Skipping any layer silently breaks the option. After editing schemas, run `yarn fix:special` so `lib/` code can reference the updated types. + +### 2. Writing and Running Tests + +**For bug fixes, always write the test case first.** Run the test to confirm it fails, then make the code change and re-run. For new features, tests can be written alongside or after. + +**Prefer integration tests over unit tests.** Cover behavior with an integration case (`configCases/`, `watchCases/`, `hotCases/`, `statsCases/`, …) that drives a real `webpack()` build whenever the behavior can be exercised that way — they catch real-world regressions a mocked unit test misses. Reach for a `*.unittest.js` only for pure helpers/utilities that a build can't naturally reach. + +Run targeted tests — `yarn jest test/` or `yarn jest -t ""`. Don't run `yarn test` unless asked. When updating snapshots (`yarn jest -u`), eyeball the diff first. See [TESTING_DOCS.md](TESTING_DOCS.md) for details. + +### 3. Adding a Changeset + +Every user-facing change needs a changeset file: + +```bash +# Create .changeset/.md with this format: +--- +"webpack": patch # or minor / major +--- + +Description of the change. +``` + +Use `patch` for bug fixes, `minor` for new features, `major` for breaking changes. Do not prefix the description with `fix:`, `feat:`, etc. + +**Keep the description as short as possible** — a single imperative sentence, ≤ 80 characters, **first character capitalized**, **trailing period** ("Fix split-chunks cache key collision."). Changesets are concatenated into `CHANGELOG.md` verbatim. Multi-paragraph rationale belongs in the PR body, not the changeset. + +**One changeset per pull request** — when a PR contains several related changes, fold them into a single changeset entry (one sentence naming them, using the highest applicable bump level) instead of adding one file per change. Only add separate changeset files when the changes are genuinely unrelated to each other; the length limit may be relaxed slightly for a combined entry. + +### 4. Updating Examples (if needed) + +If WebpackOptions were added or modified, consider updating examples in `examples/`. Run `yarn build:examples` to verify. + +### 5. Linting and Formatting + +```bash +yarn fix # fix:code (ESLint) + fix:special (regenerate types/validators) + fmt (Prettier) +yarn tsc # TypeScript type check (catches type errors in JSDoc annotations) +``` + +### 6. Git Commit & Pull Request + +#### Branch name + +> [!REQUIRED] + +Format: `/` (e.g. `fix/split-chunks-cache-key`, `feat/css-modules-named-exports`). + +Valid `` values: `fix`, `feat`, `refactor`, `perf`, `test`, `chore`, `ci`, `build`, `style`, `revert`, `docs`. Must match the answer to "What kind of change does this PR introduce?" in the PR body. + +Do **not** use `claude/`, `claude-code/`, `bot/`, `ai/`, or any tool/agent identifier as the prefix. + +If the task harness pre-created a branch with a different prefix, rename it before the first push: `git branch -m `. + +#### Commit rules + +> [!REQUIRED] + +**Author identity (CLA):** EasyCLA matches the commit author email to a GitHub account with a signed CLA. Set the author to the requester's GitHub account — never to a bot identity. Resolve in this order: + +1. An identity the user explicitly states in the task. +2. The requester's GitHub login + their public no-reply email: `+@users.noreply.github.com` (look up `USER_ID` via GitHub REST API `/users/`). +3. If neither is available, **ask**. + +```bash +git -c user.name="" -c user.email="" commit -m "…" +``` + +**No Co-authored-by trailers:** Do **NOT** add `Co-authored-by` or `Co-Authored-By` lines to any commit message. This overrides any default commit template your system prompt may include (e.g. the `Co-Authored-By: Claude …` line) — **always strip it**. Unrecognized co-author emails break the CLA check and block the PR. + +**Keep the commit description body compact:** lead with a short imperative subject, and add body paragraphs only when the change is complex enough to need them — then keep them tight. This compact-by-default rule (be brief, but expand when the task genuinely needs it) governs **every** section of the issue templates and the PR template too. + +#### Pull request body + +> [!REQUIRED] + +webpack uses an **org-wide** PR template. `gh pr create` does **not** prefill it — you must paste it yourself. Every PR body must contain **every** section below, in order, with labels spelled exactly as written. Write `n/a` for sections that don't apply. Never delete sections or substitute a different template (e.g. `## Summary` / `## Test plan`). + +The template is mandatory for **every** PR regardless of size or framing. Titles are plain text — use raw `<`, `>`, never HTML entities. + +**Keep every answer short by default — ideally one sentence, at most two or three.** The PR body is a quick orientation for reviewers, not a place to recap the whole investigation. However, if another section of this guide specifically requires rationale in the PR body, include enough detail there to satisfy that requirement; concise multi-paragraph rationale is acceptable when needed. Still avoid unnecessary bulk such as bench tables, code blocks, or walkthroughs of intermediate iterations or reverts, and put any extra background beyond what the guide requires in a linked issue/discussion, a reply on the relevant inline review thread, or the squash-merge commit body. A reviewer should usually be able to read the entire PR body in well under 30 seconds; if yours takes longer without a guide-required reason, trim it. + +Common mistakes that block PRs: + +- Using `## Summary` headings instead of `**Summary**` bold labels. +- Omitting **Use of AI** (mandatory per [webpack AI policy](https://github.com/webpack/governance/blob/main/AI_POLICY.md)). +- Omitting or mis-answering **What kind of change does this PR introduce?** (must match branch prefix). +- Dropping HTML comment hints or leaving sections blank instead of `n/a`. + +Paste the body from the fenced block below (do **not** include the fence lines themselves): + +```markdown + + +**Summary** + + + + + + + +**What kind of change does this PR introduce?** + + + +**Did you add tests for your changes?** + + + +**Does this PR introduce a breaking change?** + + + +**If relevant, what needs to be documented once your changes are merged or what have you already documented?** + + + +**Use of AI** + + +``` + +Required answer per section — **one sentence each is the target, two or three the absolute maximum**: + +- **Summary** — motivation and what problem is solved; link the related issue. When the PR actually fixes the bug or implements the feature the issue asks for, use the auto-closing form `Closes #…` / `Fixes #…` (not `Refs #…`); reserve `Refs #…` for issues the PR only relates to but does not resolve. +- **What kind of change does this PR introduce?** — one of: fix, feat, refactor, perf, test, chore, ci, build, style, revert, docs. +- **Did you add tests for your changes?** — yes/no + which test files. +- **Does this PR introduce a breaking change?** — yes/no + migration path if yes. +- **If relevant, what needs to be documented…** — list doc updates or write `n/a`. +- **Use of AI** — state that AI was used and how. Per the [webpack AI policy](https://github.com/webpack/governance/blob/main/AI_POLICY.md), omitting or misrepresenting this can get the PR closed. + +#### After push — verify PR body + +After every `git push` of a new branch, check whether a PR was auto-created (webpack has this webhook). If so, `update_pull_request` to install the full template — the auto-created body never matches. + +#### After opening the PR — wait for Copilot review + +> [!REQUIRED] + +Every webpack PR gets an automated **GitHub Copilot code review** on the initial commit and on every subsequent push. You must always wait for it and address every comment. + +1. After `create_pull_request`, subscribe to the PR (`subscribe_pr_activity`) so Copilot's review wakes the session. Do **not** poll. +2. When the review arrives, read every comment: + - If correct, push a fix in a new commit. + - If wrong, reply on the thread with a short reason — never ignore silently. +3. After every push, Copilot re-reviews. Repeat step 2. The loop ends when Copilot's latest review has zero outstanding threads. +4. Only `unsubscribe_pr_activity` once all comments are handled and CI is green, or when the user tells you to stop. diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000000..0978cc3f769 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,672 @@ +# webpack + +## 5.108.4 + +### Patch Changes + +- Speed up non-CSS-Modules parsing by not building unused selector AST nodes. (by [@alexander-akait](https://github.com/alexander-akait) in [#21324](https://github.com/webpack/webpack/pull/21324)) + +- Speed up non-CSS-Modules CSS parsing by dropping value tokens nothing reads. (by [@alexander-akait](https://github.com/alexander-akait) in [#21324](https://github.com/webpack/webpack/pull/21324)) + +- Resolve HTML asset URLs against the document ``. (by [@alexander-akait](https://github.com/alexander-akait) in [#21329](https://github.com/webpack/webpack/pull/21329)) + +- Add HTML integration tests: generateError output, ignored src, null-char parse. (by [@aryanraj45](https://github.com/aryanraj45) in [#21328](https://github.com/webpack/webpack/pull/21328)) + +- Reduce CPU and memory overhead of HTML and CSS parsing and code generation. (by [@alexander-akait](https://github.com/alexander-akait) in [#21332](https://github.com/webpack/webpack/pull/21332)) + +- Reduce HTML parser memory and CPU by skipping unused AST nodes. (by [@alexander-akait](https://github.com/alexander-akait) in [#21323](https://github.com/webpack/webpack/pull/21323)) + +- Reduce HTML parser memory by storing the AST in struct-of-arrays form. (by [@alexander-akait](https://github.com/alexander-akait) in [#21331](https://github.com/webpack/webpack/pull/21331)) + +- Key the provided-exports cache on a lazy barrel's still-lazy re-export targets. (by [@hai-x](https://github.com/hai-x) in [#21326](https://github.com/webpack/webpack/pull/21326)) + +- Default `output.globalObject` to `globalThis` for universal targets. (by [@alexander-akait](https://github.com/alexander-akait) in [#21314](https://github.com/webpack/webpack/pull/21314)) + +- Pass through `new URL(...)` directory references instead of resolving them. (by [@alexander-akait](https://github.com/alexander-akait) in [#21312](https://github.com/webpack/webpack/pull/21312)) + +## 5.108.3 + +### Patch Changes + +- Speed up CSS parsing and reduce CSS AST node memory. (by [@alexander-akait](https://github.com/alexander-akait) in [#21285](https://github.com/webpack/webpack/pull/21285)) + +- Fix HMR codegen crash for harmony accept with unresolved imports. (by [@xiaoxiaojx](https://github.com/xiaoxiaojx) in [#21302](https://github.com/webpack/webpack/pull/21302)) + +- Match harmony accept dependencies by module reference so HMR codegen handles imports without a module or chunk id. (by [@alexander-akait](https://github.com/alexander-akait) in [#21303](https://github.com/webpack/webpack/pull/21303)) + +## 5.108.2 + +### Patch Changes + +- Fix lazy barrel deferral of ungrouped side-effect imports. (by [@hai-x](https://github.com/hai-x) in [#21291](https://github.com/webpack/webpack/pull/21291)) + +- Respect the `node:` prefix for node.js core modules used as externals. (by [@alexander-akait](https://github.com/alexander-akait) in [#21286](https://github.com/webpack/webpack/pull/21286)) + +## 5.108.1 + +### Patch Changes + +- Fix invalid property access for escaped namespace imports with multi-character mangled export names. (by [@xiaoxiaojx](https://github.com/xiaoxiaojx) in [#21280](https://github.com/webpack/webpack/pull/21280)) + +- Add frames to ProfilingPlugin TracingStartedInBrowser event so the trace loads in Chrome DevTools. (by [@alexander-akait](https://github.com/alexander-akait) in [#21269](https://github.com/webpack/webpack/pull/21269)) + +## 5.108.0 + +### Minor Changes + +- Treat top-level await and `import.meta` as ES module markers, matching Node.js syntax detection so no explicit module type is needed. (by [@alexander-akait](https://github.com/alexander-akait) in [#21218](https://github.com/webpack/webpack/pull/21218)) + +- Add a `bun` target that emits ESM and externalizes `bun:*` and node.js built-in modules. (by [@alexander-akait](https://github.com/alexander-akait) in [#21248](https://github.com/webpack/webpack/pull/21248)) + +- Support CommonJS reexports via `Object.defineProperty` value and getter descriptors. (by [@alexander-akait](https://github.com/alexander-akait) in [#21129](https://github.com/webpack/webpack/pull/21129)) + +- Support JSON Schema `const` when generating CLI flags from a schema. (by [@alexander-akait](https://github.com/alexander-akait) in [#21087](https://github.com/webpack/webpack/pull/21087)) + +- Support JSON Schema `if`/`then`/`else` when generating CLI flags from a schema. (by [@alexander-akait](https://github.com/alexander-akait) in [#21087](https://github.com/webpack/webpack/pull/21087)) + +- Skip import specifiers, `require()` and `import()` calls in dead conditional branches gated by inlined imported constants (`isDEV ? A : B`), evaluated via `getCondition`. (by [@hai-x](https://github.com/hai-x) in [#21136](https://github.com/webpack/webpack/pull/21136)) + +- CSS `localIdentName` `[hash]` now resolves to the local ident hash (matching css-loader); use `[modulehash]` for the module hash. (by [@alexander-akait](https://github.com/alexander-akait) in [#21259](https://github.com/webpack/webpack/pull/21259)) + +- Add CSS parser `as` option and resolve `url()` inside HTML `style` attributes. (by [@alexander-akait](https://github.com/alexander-akait) in [#21157](https://github.com/webpack/webpack/pull/21157)) + +- Add dedicated module classes for all built-in module types. (by [@alexander-akait](https://github.com/alexander-akait) in [#21164](https://github.com/webpack/webpack/pull/21164)) + +- Support `.html`/`.css` for the default `./src` entry under the html/css experiments. (by [@alexander-akait](https://github.com/alexander-akait) in [#21039](https://github.com/webpack/webpack/pull/21039)) + +- Add `defineConfig` helper for typed configuration files. (by [@alexander-akait](https://github.com/alexander-akait) in [#21169](https://github.com/webpack/webpack/pull/21169)) + +- Add a `deno` target (with versions, e.g. `deno`, `deno2`, `deno1.40`) that emits ESM, resolves node.js built-ins via the required `node:` specifier, and keeps Deno's own import protocols (`npm:`, `jsr:`, `node:`, `http(s)://`) external. (by [@alexander-akait](https://github.com/alexander-akait) in [#21247](https://github.com/webpack/webpack/pull/21247)) + +- Use `module-import` for electron externals when the target supports ESM. (by [@alexander-akait](https://github.com/alexander-akait) in [#21184](https://github.com/webpack/webpack/pull/21184)) + +- Add `output.environment.logicalAssignment` to emit `||=` in runtime code when the target supports logical assignment operators. (by [@bjohansebas](https://github.com/bjohansebas) in [#21219](https://github.com/webpack/webpack/pull/21219)) + +- Resolve and rewrite asset URLs inside ` + + + + +" +`; diff --git a/test/configCases/html/iframe-srcdoc-css/__snapshots__/ConfigTest.snap b/test/configCases/html/iframe-srcdoc-css/__snapshots__/ConfigTest.snap new file mode 100644 index 00000000000..9274159d033 --- /dev/null +++ b/test/configCases/html/iframe-srcdoc-css/__snapshots__/ConfigTest.snap @@ -0,0 +1,20 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigTestCases html iframe-srcdoc-css exported tests should process CSS inside + + + + +" +`; diff --git a/test/configCases/html/iframe-srcdoc-css/imported.css b/test/configCases/html/iframe-srcdoc-css/imported.css new file mode 100644 index 00000000000..575d19f7b0e --- /dev/null +++ b/test/configCases/html/iframe-srcdoc-css/imported.css @@ -0,0 +1,3 @@ +body { + color: red; +} diff --git a/test/configCases/html/iframe-srcdoc-css/index.js b/test/configCases/html/iframe-srcdoc-css/index.js new file mode 100644 index 00000000000..43e1ceb5ba3 --- /dev/null +++ b/test/configCases/html/iframe-srcdoc-css/index.js @@ -0,0 +1,28 @@ +import page from "./page.html"; + +it("should process CSS inside + + + + diff --git a/test/configCases/html/iframe-srcdoc-css/pixel.png b/test/configCases/html/iframe-srcdoc-css/pixel.png new file mode 100644 index 00000000000..e69de29bb2d diff --git a/test/configCases/html/iframe-srcdoc-css/webpack.config.js b/test/configCases/html/iframe-srcdoc-css/webpack.config.js new file mode 100644 index 00000000000..fe3ab4ea595 --- /dev/null +++ b/test/configCases/html/iframe-srcdoc-css/webpack.config.js @@ -0,0 +1,15 @@ +"use strict"; + +/** @type {import("../../../../").Configuration} */ +module.exports = { + devtool: false, + target: "web", + output: { + pathinfo: false, + assetModuleFilename: "handled-[name][ext]" + }, + experiments: { + html: true, + css: true + } +}; diff --git a/test/configCases/html/iframe-srcdoc-entry/image.png b/test/configCases/html/iframe-srcdoc-entry/image.png new file mode 100644 index 00000000000..e69de29bb2d diff --git a/test/configCases/html/iframe-srcdoc-entry/page.html b/test/configCases/html/iframe-srcdoc-entry/page.html new file mode 100644 index 00000000000..bd7d9f2d8cd --- /dev/null +++ b/test/configCases/html/iframe-srcdoc-entry/page.html @@ -0,0 +1,7 @@ + + +srcdoc entry + + + + diff --git a/test/configCases/html/iframe-srcdoc-entry/test.config.js b/test/configCases/html/iframe-srcdoc-entry/test.config.js new file mode 100644 index 00000000000..b3988929bc0 --- /dev/null +++ b/test/configCases/html/iframe-srcdoc-entry/test.config.js @@ -0,0 +1,9 @@ +"use strict"; + +// The HTML entry has no top-level `it(...)` body — assertions live in the +// emitted `test.js` asset, which the harness loads directly. +module.exports = { + findBundle() { + return ["./test.js"]; + } +}; diff --git a/test/configCases/html/iframe-srcdoc-entry/test.js b/test/configCases/html/iframe-srcdoc-entry/test.js new file mode 100644 index 00000000000..37e86cbaea9 --- /dev/null +++ b/test/configCases/html/iframe-srcdoc-entry/test.js @@ -0,0 +1,21 @@ +const fs = require("fs"); +const path = require("path"); + +it("emits the entry .html (extract default) but not the inline srcdoc module", () => { + const files = fs.readdirSync(__dirname); + const htmlFiles = files.filter((f) => f.endsWith(".html")); + + // The not-`"inline"` branch: the HTML entry is emitted as a standalone file. + expect(htmlFiles).toContain("page.html"); + + // The `"inline"` branch: the nested ` + + + +" +`; diff --git a/test/configCases/html/iframe-srcdoc-script/__snapshots__/ConfigTest.snap b/test/configCases/html/iframe-srcdoc-script/__snapshots__/ConfigTest.snap new file mode 100644 index 00000000000..354f448bf06 --- /dev/null +++ b/test/configCases/html/iframe-srcdoc-script/__snapshots__/ConfigTest.snap @@ -0,0 +1,13 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigTestCases html iframe-srcdoc-script exported tests should bundle \\"> + + + +" +`; diff --git a/test/configCases/html/iframe-srcdoc-script/app.js b/test/configCases/html/iframe-srcdoc-script/app.js new file mode 100644 index 00000000000..42ffdc8d996 --- /dev/null +++ b/test/configCases/html/iframe-srcdoc-script/app.js @@ -0,0 +1 @@ +globalThis.__SRCDOC_SCRIPT__ = "loaded"; diff --git a/test/configCases/html/iframe-srcdoc-script/index.js b/test/configCases/html/iframe-srcdoc-script/index.js new file mode 100644 index 00000000000..d74979f9689 --- /dev/null +++ b/test/configCases/html/iframe-srcdoc-script/index.js @@ -0,0 +1,28 @@ +import page from "./page.html"; + +it("should bundle ` body is bundled too (it passes the + // `SRCDOC_ASSET_REGEXP` pre-filter via the `=`), so its raw body is gone. + expect(page).not.toContain("var marker = 1;"); + + // Each script-bearing srcdoc spins up a nested data:text/html module. + const ids = __STATS__.modules.map((m) => m.identifier || m.name || ""); + expect( + ids.filter((id) => id.includes("data:text/html")).length + ).toBeGreaterThanOrEqual(2); + + // The bundled scripts become their own emitted JS chunks (named + // `__html__`). + const names = __STATS__.assets.map((a) => a.name); + expect(names.some((n) => /__html_[0-9a-f]+_\d+\./.test(n))).toBe(true); +}); diff --git a/test/configCases/html/iframe-srcdoc-script/page.html b/test/configCases/html/iframe-srcdoc-script/page.html new file mode 100644 index 00000000000..95386bf9358 --- /dev/null +++ b/test/configCases/html/iframe-srcdoc-script/page.html @@ -0,0 +1,8 @@ + + +srcdoc script + + + + + diff --git a/test/configCases/html/iframe-srcdoc-script/webpack.config.js b/test/configCases/html/iframe-srcdoc-script/webpack.config.js new file mode 100644 index 00000000000..b1028cab9b8 --- /dev/null +++ b/test/configCases/html/iframe-srcdoc-script/webpack.config.js @@ -0,0 +1,17 @@ +"use strict"; + +/** @type {import("../../../../").Configuration} */ +module.exports = { + devtool: false, + target: "web", + output: { + pathinfo: false, + assetModuleFilename: "handled-[name][ext]" + }, + optimization: { + chunkIds: "named" + }, + experiments: { + html: true + } +}; diff --git a/test/configCases/html/iframe-srcdoc/__snapshots__/ConfigCacheTest.snap b/test/configCases/html/iframe-srcdoc/__snapshots__/ConfigCacheTest.snap new file mode 100644 index 00000000000..964c19510de --- /dev/null +++ b/test/configCases/html/iframe-srcdoc/__snapshots__/ConfigCacheTest.snap @@ -0,0 +1,24 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigCacheTestCases html iframe-srcdoc exported tests should rewrite asset URLs inside + + + + + + + + + + + + + + +" +`; diff --git a/test/configCases/html/iframe-srcdoc/__snapshots__/ConfigTest.snap b/test/configCases/html/iframe-srcdoc/__snapshots__/ConfigTest.snap new file mode 100644 index 00000000000..cb9c20da66e --- /dev/null +++ b/test/configCases/html/iframe-srcdoc/__snapshots__/ConfigTest.snap @@ -0,0 +1,24 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigTestCases html iframe-srcdoc exported tests should rewrite asset URLs inside + + + + + + + + + + + + + + +" +`; diff --git a/test/configCases/html/iframe-srcdoc/index.js b/test/configCases/html/iframe-srcdoc/index.js new file mode 100644 index 00000000000..b660d5da061 --- /dev/null +++ b/test/configCases/html/iframe-srcdoc/index.js @@ -0,0 +1,98 @@ +import page from "./page.html"; + +it("should rewrite asset URLs inside + + + + + + + + + + + + + + diff --git a/test/configCases/html/iframe-srcdoc/pixel.png b/test/configCases/html/iframe-srcdoc/pixel.png new file mode 100644 index 00000000000..e69de29bb2d diff --git a/test/configCases/html/iframe-srcdoc/webpack.config.js b/test/configCases/html/iframe-srcdoc/webpack.config.js new file mode 100644 index 00000000000..fe3ab4ea595 --- /dev/null +++ b/test/configCases/html/iframe-srcdoc/webpack.config.js @@ -0,0 +1,15 @@ +"use strict"; + +/** @type {import("../../../../").Configuration} */ +module.exports = { + devtool: false, + target: "web", + output: { + pathinfo: false, + assetModuleFilename: "handled-[name][ext]" + }, + experiments: { + html: true, + css: true + } +}; diff --git a/test/configCases/html/ignored-source/__snapshots__/ConfigCacheTest.snap b/test/configCases/html/ignored-source/__snapshots__/ConfigCacheTest.snap new file mode 100644 index 00000000000..81571f1636a --- /dev/null +++ b/test/configCases/html/ignored-source/__snapshots__/ConfigCacheTest.snap @@ -0,0 +1,13 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigCacheTestCases html ignored-source exported tests should replace an alias-false src with the ignored data URL 1`] = ` +" + +ignored-source + +\\"resolved\\" +\\"ignored\\" + + +" +`; diff --git a/test/configCases/html/ignored-source/__snapshots__/ConfigTest.snap b/test/configCases/html/ignored-source/__snapshots__/ConfigTest.snap new file mode 100644 index 00000000000..91b6e2e3059 --- /dev/null +++ b/test/configCases/html/ignored-source/__snapshots__/ConfigTest.snap @@ -0,0 +1,13 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigTestCases html ignored-source exported tests should replace an alias-false src with the ignored data URL 1`] = ` +" + +ignored-source + +\\"resolved\\" +\\"ignored\\" + + +" +`; diff --git a/test/configCases/html/ignored-source/image.png b/test/configCases/html/ignored-source/image.png new file mode 100644 index 00000000000..b74b839e2b8 Binary files /dev/null and b/test/configCases/html/ignored-source/image.png differ diff --git a/test/configCases/html/ignored-source/index.js b/test/configCases/html/ignored-source/index.js new file mode 100644 index 00000000000..2109edab753 --- /dev/null +++ b/test/configCases/html/ignored-source/index.js @@ -0,0 +1,5 @@ +import page from "./page.html"; + +it("should replace an alias-false src with the ignored data URL", () => { + expect(page).toMatchSnapshot(); +}); diff --git a/test/configCases/html/ignored-source/page.html b/test/configCases/html/ignored-source/page.html new file mode 100644 index 00000000000..85312e3de74 --- /dev/null +++ b/test/configCases/html/ignored-source/page.html @@ -0,0 +1,8 @@ + + +ignored-source + +resolved +ignored + + diff --git a/test/configCases/html/ignored-source/webpack.config.js b/test/configCases/html/ignored-source/webpack.config.js new file mode 100644 index 00000000000..6fca060d516 --- /dev/null +++ b/test/configCases/html/ignored-source/webpack.config.js @@ -0,0 +1,14 @@ +"use strict"; + +/** @type {import("../../../../").Configuration} */ +module.exports = { + target: "web", + resolve: { + alias: { + "ignored.png": false + } + }, + experiments: { + html: true + } +}; diff --git a/test/configCases/html/inline-script-classic/__snapshots__/ConfigCacheTest.snap b/test/configCases/html/inline-script-classic/__snapshots__/ConfigCacheTest.snap new file mode 100644 index 00000000000..bdecc954834 --- /dev/null +++ b/test/configCases/html/inline-script-classic/__snapshots__/ConfigCacheTest.snap @@ -0,0 +1,179 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigCacheTestCases html inline-script-classic exported tests should emit IIFE-wrapped chunks for inline + + + + + + +

Hi

+ + +" +`; diff --git a/test/configCases/html/inline-script-classic/__snapshots__/ConfigTest.snap b/test/configCases/html/inline-script-classic/__snapshots__/ConfigTest.snap new file mode 100644 index 00000000000..a60ab586826 --- /dev/null +++ b/test/configCases/html/inline-script-classic/__snapshots__/ConfigTest.snap @@ -0,0 +1,179 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigTestCases html inline-script-classic exported tests should emit IIFE-wrapped chunks for inline + + + + + + +

Hi

+ + +" +`; diff --git a/test/configCases/html/inline-script-classic/index.js b/test/configCases/html/inline-script-classic/index.js new file mode 100644 index 00000000000..78c5405a015 --- /dev/null +++ b/test/configCases/html/inline-script-classic/index.js @@ -0,0 +1,111 @@ +const fs = require("fs"); +const path = require("path"); + +const page = require("./page.html"); + +const readChunk = (name) => + fs.readFileSync(path.resolve(__dirname, name), "utf-8"); + +// `require("./page.html")` always returns the HTML body as a string, but +// static analysis can't see through the HTML module type, so normalize +// once at the top so the rest of the file accesses a concrete string. +const pageContent = typeof page === "string" ? page : ""; + +// `String.prototype.matchAll` requires Node 12+, but this test stays on +// Node 10-compatible CJS so we collect all matches via a `regex.exec` +// loop with the `g` flag. +const collectMatches = (str, regex) => { + const out = []; + let m; + while ((m = regex.exec(str)) !== null) out.push(m); + return out; +}; + +it("should rewrite inline + + + + + + +

Hi

+ + diff --git a/test/configCases/html/inline-script-classic/webpack.config.js b/test/configCases/html/inline-script-classic/webpack.config.js new file mode 100644 index 00000000000..772bb8f208e --- /dev/null +++ b/test/configCases/html/inline-script-classic/webpack.config.js @@ -0,0 +1,28 @@ +"use strict"; + +// No `experiments.outputModule` and no `output.module` — emitted chunks are +// classic IIFE-wrapped scripts. In this mode the parser must NOT auto- +// upgrade classic inline ` + + + + + + + +

Hi

+ + + +" +`; diff --git a/test/configCases/html/inline-script/__snapshots__/ConfigTest.snap b/test/configCases/html/inline-script/__snapshots__/ConfigTest.snap new file mode 100644 index 00000000000..1a51f9d64a0 --- /dev/null +++ b/test/configCases/html/inline-script/__snapshots__/ConfigTest.snap @@ -0,0 +1,39 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigTestCases html inline-script exported tests should bundle inline + + + + + + + +

Hi

+ + + +" +`; diff --git a/test/configCases/html/inline-script/index.js b/test/configCases/html/inline-script/index.js new file mode 100644 index 00000000000..5e05031bdff --- /dev/null +++ b/test/configCases/html/inline-script/index.js @@ -0,0 +1,106 @@ +import fs from "fs"; +import path from "path"; +import { fileURLToPath } from "url"; + +import page from "./page.html"; + +const here = path.dirname(fileURLToPath(import.meta.url)); + +const readChunk = (name) => fs.readFileSync(path.resolve(here, name), "utf-8"); + +// `import page from "./page.html"` always returns the HTML body as a +// string, but static analysis can't see through the HTML module type, so +// it flags property access on `page` as possibly-undefined. Normalize +// once at the top so the rest of the file accesses a concrete string. +const pageContent = typeof page === "string" ? page : ""; + +// Document-order list of every inline-script chunk url emitted into the page. +const scriptChunkUrls = [ + ...pageContent.matchAll(/]*\bsrc="(__html_[^"]+)"/g) +].map((m) => m[1]); + +it("should bundle inline + + + + + + + +

Hi

+ + + diff --git a/test/configCases/html/inline-script/test.filter.js b/test/configCases/html/inline-script/test.filter.js new file mode 100644 index 00000000000..df2522abe50 --- /dev/null +++ b/test/configCases/html/inline-script/test.filter.js @@ -0,0 +1,9 @@ +"use strict"; + +// Tests use `import.meta.url` to locate emitted chunks at runtime, which +// requires the bundle to run as a real ES module. Jest's ESM support +// (via `--experimental-vm-modules`) needs Node 12+. +module.exports = function filter() { + const major = Number(process.versions.node.split(".")[0]); + return major >= 12; +}; diff --git a/test/configCases/html/inline-script/webpack.config.js b/test/configCases/html/inline-script/webpack.config.js new file mode 100644 index 00000000000..73f5fad1ec0 --- /dev/null +++ b/test/configCases/html/inline-script/webpack.config.js @@ -0,0 +1,30 @@ +"use strict"; + +/** @type {import("../../../../").Configuration} */ +module.exports = { + target: ["web", "es2022"], + node: { + __dirname: false, + __filename: false + }, + externalsPresets: { + node: true + }, + module: { + parser: { + javascript: { + importMeta: false + } + } + }, + output: { + chunkFilename: "[name].chunk.js" + }, + optimization: { + chunkIds: "named" + }, + experiments: { + html: true, + outputModule: true + } +}; diff --git a/test/configCases/html/invisible-space/__snapshots__/ConfigCacheTest.snap b/test/configCases/html/invisible-space/__snapshots__/ConfigCacheTest.snap new file mode 100644 index 00000000000..900b8594208 --- /dev/null +++ b/test/configCases/html/invisible-space/__snapshots__/ConfigCacheTest.snap @@ -0,0 +1,6 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigCacheTestCases html invisible-space exported tests should compile and export html as string 1`] = ` +"
  • \\\\u2028 - 
 Text 

  • \\\\u2029 - 
 Text 

+" +`; diff --git a/test/configCases/html/invisible-space/__snapshots__/ConfigTest.snap b/test/configCases/html/invisible-space/__snapshots__/ConfigTest.snap new file mode 100644 index 00000000000..3d8465e3c72 --- /dev/null +++ b/test/configCases/html/invisible-space/__snapshots__/ConfigTest.snap @@ -0,0 +1,6 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigTestCases html invisible-space exported tests should compile and export html as string 1`] = ` +"
  • \\\\u2028 - 
 Text 

  • \\\\u2029 - 
 Text 

+" +`; diff --git a/test/configCases/html/invisible-space/index.js b/test/configCases/html/invisible-space/index.js new file mode 100644 index 00000000000..e52e8786661 --- /dev/null +++ b/test/configCases/html/invisible-space/index.js @@ -0,0 +1,5 @@ +import page from "./page.html"; + +it("should compile and export html as string", () => { + expect(page).toMatchSnapshot(); +}); diff --git a/test/configCases/html/invisible-space/page.html b/test/configCases/html/invisible-space/page.html new file mode 100644 index 00000000000..eb5f2ab99b9 --- /dev/null +++ b/test/configCases/html/invisible-space/page.html @@ -0,0 +1 @@ +
  • \u2028 - 
 Text 

  • \u2029 - 
 Text 

diff --git a/test/configCases/html/invisible-space/webpack.config.js b/test/configCases/html/invisible-space/webpack.config.js new file mode 100644 index 00000000000..bc4cbc8fe56 --- /dev/null +++ b/test/configCases/html/invisible-space/webpack.config.js @@ -0,0 +1,9 @@ +"use strict"; + +/** @type {import("../../../../").Configuration} */ +module.exports = { + target: "web", + experiments: { + html: true + } +}; diff --git a/test/configCases/html/js-css-html/__snapshots__/ConfigCacheTest.snap b/test/configCases/html/js-css-html/__snapshots__/ConfigCacheTest.snap new file mode 100644 index 00000000000..a038f2751d8 --- /dev/null +++ b/test/configCases/html/js-css-html/__snapshots__/ConfigCacheTest.snap @@ -0,0 +1,33 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigCacheTestCases html js-css-html exported tests should work 1`] = `"https://test.cases/path/handled-image.png"`; + +exports[`ConfigCacheTestCases html js-css-html exported tests should work 2`] = ` +" + + + + + + Document + + +\\"test\\" + + +" +`; + +exports[`ConfigCacheTestCases html js-css-html exported tests should work 3`] = ` +Array [ + "/*!***********************!*\\\\ + !*** css ./style.css ***! + \\\\***********************/ +.class { + background: url(handled-image.png); +} + +", +] +`; diff --git a/test/configCases/html/js-css-html/__snapshots__/ConfigTest.snap b/test/configCases/html/js-css-html/__snapshots__/ConfigTest.snap new file mode 100644 index 00000000000..4ae3d0dee25 --- /dev/null +++ b/test/configCases/html/js-css-html/__snapshots__/ConfigTest.snap @@ -0,0 +1,33 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigTestCases html js-css-html exported tests should work 1`] = `"https://test.cases/path/handled-image.png"`; + +exports[`ConfigTestCases html js-css-html exported tests should work 2`] = ` +" + + + + + + Document + + +\\"test\\" + + +" +`; + +exports[`ConfigTestCases html js-css-html exported tests should work 3`] = ` +Array [ + "/*!***********************!*\\\\ + !*** css ./style.css ***! + \\\\***********************/ +.class { + background: url(handled-image.png); +} + +", +] +`; diff --git a/test/configCases/html/js-css-html/image.png b/test/configCases/html/js-css-html/image.png new file mode 100644 index 00000000000..e69de29bb2d diff --git a/test/configCases/html/js-css-html/index.js b/test/configCases/html/js-css-html/index.js new file mode 100644 index 00000000000..5b8b4e2b7a5 --- /dev/null +++ b/test/configCases/html/js-css-html/index.js @@ -0,0 +1,19 @@ +import html from "./page.html"; +import "./style.css"; + +it("should work", () => { + const myUrl = new URL("./image.png", import.meta.url); + + expect(myUrl).toMatchSnapshot(); + expect(html).toMatchSnapshot(); + + const links = document.getElementsByTagName("link"); + const css = []; + + // Skip first because import it by default + for (const link of links.slice(1)) { + css.push(link.sheet.css); + } + + expect(css).toMatchSnapshot(); +}); diff --git a/test/configCases/html/js-css-html/page.html b/test/configCases/html/js-css-html/page.html new file mode 100644 index 00000000000..4ce6d0c4949 --- /dev/null +++ b/test/configCases/html/js-css-html/page.html @@ -0,0 +1,13 @@ + + + + + + + Document + + +test + + diff --git a/test/configCases/html/js-css-html/style.css b/test/configCases/html/js-css-html/style.css new file mode 100644 index 00000000000..3c6140f6e35 --- /dev/null +++ b/test/configCases/html/js-css-html/style.css @@ -0,0 +1,3 @@ +.class { + background: url("./image.png"); +} diff --git a/test/configCases/html/js-css-html/test.config.js b/test/configCases/html/js-css-html/test.config.js new file mode 100644 index 00000000000..dbfd4316888 --- /dev/null +++ b/test/configCases/html/js-css-html/test.config.js @@ -0,0 +1,10 @@ +"use strict"; + +module.exports = { + moduleScope(scope) { + const link = scope.window.document.createElement("link"); + link.rel = "stylesheet"; + link.href = `bundle${scope.__STATS_I__}.css`; + scope.window.document.head.appendChild(link); + } +}; diff --git a/test/configCases/html/js-css-html/test.filter.js b/test/configCases/html/js-css-html/test.filter.js new file mode 100644 index 00000000000..f5f225500d8 --- /dev/null +++ b/test/configCases/html/js-css-html/test.filter.js @@ -0,0 +1,10 @@ +"use strict"; + +// Test uses `new URL("./image.png", import.meta.url)` to locate the +// emitted asset at runtime, which requires the bundle to run as a real +// ES module. Jest's ESM support (via `--experimental-vm-modules`) needs +// Node 12+. +module.exports = function filter() { + const major = Number(process.versions.node.split(".")[0]); + return major >= 12; +}; diff --git a/test/configCases/html/js-css-html/webpack.config.js b/test/configCases/html/js-css-html/webpack.config.js new file mode 100644 index 00000000000..b57b96278fc --- /dev/null +++ b/test/configCases/html/js-css-html/webpack.config.js @@ -0,0 +1,13 @@ +"use strict"; + +/** @type {import("../../../../").Configuration} */ +module.exports = { + target: "web", + output: { + assetModuleFilename: "handled-[name][ext]" + }, + experiments: { + html: true, + css: true + } +}; diff --git a/test/configCases/html/legacy-and-obsolete-sources/__snapshots__/ConfigCacheTest.snap b/test/configCases/html/legacy-and-obsolete-sources/__snapshots__/ConfigCacheTest.snap new file mode 100644 index 00000000000..4c987ad73ea --- /dev/null +++ b/test/configCases/html/legacy-and-obsolete-sources/__snapshots__/ConfigCacheTest.snap @@ -0,0 +1,25 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigCacheTestCases html legacy-and-obsolete-sources exported tests should resolve legacy and obsolete source attributes 1`] = ` +" + + +Legacy and obsolete sources + + + + + + + + + + + + + + + + +" +`; diff --git a/test/configCases/html/legacy-and-obsolete-sources/__snapshots__/ConfigTest.snap b/test/configCases/html/legacy-and-obsolete-sources/__snapshots__/ConfigTest.snap new file mode 100644 index 00000000000..599834a1d56 --- /dev/null +++ b/test/configCases/html/legacy-and-obsolete-sources/__snapshots__/ConfigTest.snap @@ -0,0 +1,25 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigTestCases html legacy-and-obsolete-sources exported tests should resolve legacy and obsolete source attributes 1`] = ` +" + + +Legacy and obsolete sources + + + + + + + + + + + + + + + + +" +`; diff --git a/test/configCases/html/legacy-and-obsolete-sources/index.js b/test/configCases/html/legacy-and-obsolete-sources/index.js new file mode 100644 index 00000000000..88968a812e5 --- /dev/null +++ b/test/configCases/html/legacy-and-obsolete-sources/index.js @@ -0,0 +1,20 @@ +import page from "./page.html"; + +it("should resolve legacy and obsolete source attributes", () => { + expect(page).not.toContain("./ref.png"); + // Legacy preview-image hints + expect(page).toMatch(//); + expect(page).toMatch(//); + // Obsolete / + expect(page).toMatch(/classid="[0-9a-f]+\.png"/); + expect(page).toMatch( + // + ); + expect(page).toMatch(/code="[0-9a-f]+\.png"/); + expect(page).toMatch(/object="[0-9a-f]+\.png"/); + // MathML + expect(page).toMatch(//); + // A without valuetype="ref" is an opaque string, left untouched + expect(page).toContain('value="./skip.png"'); + expect(page).toMatchSnapshot(); +}); diff --git a/test/configCases/html/legacy-and-obsolete-sources/page.html b/test/configCases/html/legacy-and-obsolete-sources/page.html new file mode 100644 index 00000000000..eac7fcf1a4d --- /dev/null +++ b/test/configCases/html/legacy-and-obsolete-sources/page.html @@ -0,0 +1,20 @@ + + + +Legacy and obsolete sources + + + + + + + + + + + + + + + + diff --git a/test/configCases/html/legacy-and-obsolete-sources/ref.png b/test/configCases/html/legacy-and-obsolete-sources/ref.png new file mode 100644 index 00000000000..91a99b94e23 Binary files /dev/null and b/test/configCases/html/legacy-and-obsolete-sources/ref.png differ diff --git a/test/configCases/html/legacy-and-obsolete-sources/webpack.config.js b/test/configCases/html/legacy-and-obsolete-sources/webpack.config.js new file mode 100644 index 00000000000..2d275504b64 --- /dev/null +++ b/test/configCases/html/legacy-and-obsolete-sources/webpack.config.js @@ -0,0 +1,10 @@ +"use strict"; + +/** @type {import("../../../../").Configuration} */ +module.exports = { + devtool: false, + target: "web", + experiments: { + html: true + } +}; diff --git a/test/configCases/html/legacy-background/__snapshots__/ConfigCacheTest.snap b/test/configCases/html/legacy-background/__snapshots__/ConfigCacheTest.snap new file mode 100644 index 00000000000..42a1d06aa82 --- /dev/null +++ b/test/configCases/html/legacy-background/__snapshots__/ConfigCacheTest.snap @@ -0,0 +1,19 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigCacheTestCases html legacy-background exported tests should resolve the deprecated \`background\` attribute on body/table/td/th 1`] = ` +" + +Legacy background + + + + + + + +
headcell
+ + + +" +`; diff --git a/test/configCases/html/legacy-background/__snapshots__/ConfigTest.snap b/test/configCases/html/legacy-background/__snapshots__/ConfigTest.snap new file mode 100644 index 00000000000..dae79503d87 --- /dev/null +++ b/test/configCases/html/legacy-background/__snapshots__/ConfigTest.snap @@ -0,0 +1,19 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigTestCases html legacy-background exported tests should resolve the deprecated \`background\` attribute on body/table/td/th 1`] = ` +" + +Legacy background + + + + + + + +
headcell
+ + + +" +`; diff --git a/test/configCases/html/legacy-background/bg.png b/test/configCases/html/legacy-background/bg.png new file mode 100644 index 00000000000..91a99b94e23 Binary files /dev/null and b/test/configCases/html/legacy-background/bg.png differ diff --git a/test/configCases/html/legacy-background/index.js b/test/configCases/html/legacy-background/index.js new file mode 100644 index 00000000000..e6359fc7683 --- /dev/null +++ b/test/configCases/html/legacy-background/index.js @@ -0,0 +1,10 @@ +import page from "./page.html"; + +it("should resolve the deprecated `background` attribute on body/table/td/th", () => { + expect(page).not.toContain("./bg.png"); + expect(page).toMatch(//); + expect(page).toMatch(//); + expect(page).toMatch(/
/); + expect(page).toMatch(//); + expect(page).toMatchSnapshot(); +}); diff --git a/test/configCases/html/legacy-background/page.html b/test/configCases/html/legacy-background/page.html new file mode 100644 index 00000000000..c51bea73105 --- /dev/null +++ b/test/configCases/html/legacy-background/page.html @@ -0,0 +1,14 @@ + + +Legacy background + + + + + + + +
headcell
+ + + diff --git a/test/configCases/html/legacy-background/webpack.config.js b/test/configCases/html/legacy-background/webpack.config.js new file mode 100644 index 00000000000..2d275504b64 --- /dev/null +++ b/test/configCases/html/legacy-background/webpack.config.js @@ -0,0 +1,10 @@ +"use strict"; + +/** @type {import("../../../../").Configuration} */ +module.exports = { + devtool: false, + target: "web", + experiments: { + html: true + } +}; diff --git a/test/configCases/html/link/__snapshots__/ConfigCacheTest.snap b/test/configCases/html/link/__snapshots__/ConfigCacheTest.snap new file mode 100644 index 00000000000..35ce018131b --- /dev/null +++ b/test/configCases/html/link/__snapshots__/ConfigCacheTest.snap @@ -0,0 +1,89 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigCacheTestCases html link exported tests should handle link tag 1`] = ` +" + + +Link Tag Test + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +" +`; + +exports[`ConfigCacheTestCases html link exported tests should handle link tag 2`] = ` +Array [ + "/*!***********************!*\\\\ + !*** css ./style.css ***! + \\\\***********************/ +.foo { + background: url(handled-image.png); + color: red; +} + +/*!***************************!*\\\\ + !*** css ./alt-style.css ***! + \\\\***************************/ +.bar { + background: url(handled-icon.png); + color: blue; +} + +", +] +`; diff --git a/test/configCases/html/link/__snapshots__/ConfigTest.snap b/test/configCases/html/link/__snapshots__/ConfigTest.snap new file mode 100644 index 00000000000..8c9086c83f9 --- /dev/null +++ b/test/configCases/html/link/__snapshots__/ConfigTest.snap @@ -0,0 +1,89 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigTestCases html link exported tests should handle link tag 1`] = ` +" + + +Link Tag Test + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +" +`; + +exports[`ConfigTestCases html link exported tests should handle link tag 2`] = ` +Array [ + "/*!***********************!*\\\\ + !*** css ./style.css ***! + \\\\***********************/ +.foo { + background: url(handled-image.png); + color: red; +} + +/*!***************************!*\\\\ + !*** css ./alt-style.css ***! + \\\\***************************/ +.bar { + background: url(handled-icon.png); + color: blue; +} + +", +] +`; diff --git a/test/configCases/html/link/alt-style.css b/test/configCases/html/link/alt-style.css new file mode 100644 index 00000000000..7d3168d1af2 --- /dev/null +++ b/test/configCases/html/link/alt-style.css @@ -0,0 +1,4 @@ +.bar { + background: url(./icon.png); + color: blue; +} diff --git a/test/configCases/html/link/font.woff2 b/test/configCases/html/link/font.woff2 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/test/configCases/html/link/icon.png b/test/configCases/html/link/icon.png new file mode 100644 index 00000000000..b74b839e2b8 Binary files /dev/null and b/test/configCases/html/link/icon.png differ diff --git a/test/configCases/html/link/image.png b/test/configCases/html/link/image.png new file mode 100644 index 00000000000..b74b839e2b8 Binary files /dev/null and b/test/configCases/html/link/image.png differ diff --git a/test/configCases/html/link/index.js b/test/configCases/html/link/index.js new file mode 100644 index 00000000000..c2accf60b4c --- /dev/null +++ b/test/configCases/html/link/index.js @@ -0,0 +1,20 @@ +import page from "./page.html"; +import "./style.css"; +import "./alt-style.css"; + +it("should handle link tag", () => { + expect(page).toMatchSnapshot(); + + // webpackIgnore leaves the stylesheet link untouched. + expect(page).toContain(''); + + const links = document.getElementsByTagName("link"); + const css = []; + + // Skip first because import it by default + for (const link of links.slice(1)) { + css.push(link.sheet.css); + } + + expect(css).toMatchSnapshot(); +}); diff --git a/test/configCases/html/link/manifest.webmanifest b/test/configCases/html/link/manifest.webmanifest new file mode 100644 index 00000000000..9e26dfeeb6e --- /dev/null +++ b/test/configCases/html/link/manifest.webmanifest @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/test/configCases/html/link/module.mjs b/test/configCases/html/link/module.mjs new file mode 100644 index 00000000000..46d3ca8c61f --- /dev/null +++ b/test/configCases/html/link/module.mjs @@ -0,0 +1 @@ +export const value = 42; diff --git a/test/configCases/html/link/page.html b/test/configCases/html/link/page.html new file mode 100644 index 00000000000..177c13c6e61 --- /dev/null +++ b/test/configCases/html/link/page.html @@ -0,0 +1,62 @@ + + + +Link Tag Test + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/configCases/html/link/prefetch.png b/test/configCases/html/link/prefetch.png new file mode 100644 index 00000000000..b74b839e2b8 Binary files /dev/null and b/test/configCases/html/link/prefetch.png differ diff --git a/test/configCases/html/link/preload.png b/test/configCases/html/link/preload.png new file mode 100644 index 00000000000..b74b839e2b8 Binary files /dev/null and b/test/configCases/html/link/preload.png differ diff --git a/test/configCases/html/link/style.css b/test/configCases/html/link/style.css new file mode 100644 index 00000000000..c0f9bec22dd --- /dev/null +++ b/test/configCases/html/link/style.css @@ -0,0 +1,4 @@ +.foo { + background: url(./image.png); + color: red; +} diff --git a/test/configCases/html/link/test.config.js b/test/configCases/html/link/test.config.js new file mode 100644 index 00000000000..dbfd4316888 --- /dev/null +++ b/test/configCases/html/link/test.config.js @@ -0,0 +1,10 @@ +"use strict"; + +module.exports = { + moduleScope(scope) { + const link = scope.window.document.createElement("link"); + link.rel = "stylesheet"; + link.href = `bundle${scope.__STATS_I__}.css`; + scope.window.document.head.appendChild(link); + } +}; diff --git a/test/configCases/html/link/webpack.config.js b/test/configCases/html/link/webpack.config.js new file mode 100644 index 00000000000..28c452814aa --- /dev/null +++ b/test/configCases/html/link/webpack.config.js @@ -0,0 +1,14 @@ +"use strict"; + +/** @type {import("../../../../").Configuration} */ +module.exports = { + devtool: false, + target: "web", + output: { + assetModuleFilename: "handled-[name][ext]" + }, + experiments: { + html: true, + css: true + } +}; diff --git a/test/configCases/html/module-sibling-type/__snapshots__/ConfigCacheTest.snap b/test/configCases/html/module-sibling-type/__snapshots__/ConfigCacheTest.snap new file mode 100644 index 00000000000..0766523a439 --- /dev/null +++ b/test/configCases/html/module-sibling-type/__snapshots__/ConfigCacheTest.snap @@ -0,0 +1,13 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigCacheTestCases html module-sibling-type exported tests rewrites type=module in place when cloning a native module + +

Hello

+ +" +`; diff --git a/test/configCases/html/module-sibling-type/__snapshots__/ConfigTest.snap b/test/configCases/html/module-sibling-type/__snapshots__/ConfigTest.snap new file mode 100644 index 00000000000..3c3f663b488 --- /dev/null +++ b/test/configCases/html/module-sibling-type/__snapshots__/ConfigTest.snap @@ -0,0 +1,13 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigTestCases html module-sibling-type exported tests rewrites type=module in place when cloning a native module + +

Hello

+ +" +`; diff --git a/test/configCases/html/module-sibling-type/entry.js b/test/configCases/html/module-sibling-type/entry.js new file mode 100644 index 00000000000..513b084bc5a --- /dev/null +++ b/test/configCases/html/module-sibling-type/entry.js @@ -0,0 +1 @@ +export default "entry"; diff --git a/test/configCases/html/module-sibling-type/index.js b/test/configCases/html/module-sibling-type/index.js new file mode 100644 index 00000000000..25324380f66 --- /dev/null +++ b/test/configCases/html/module-sibling-type/index.js @@ -0,0 +1,21 @@ +import page from "./page.html"; + +it("rewrites type=module in place when cloning a native module + +

Hello

+ diff --git a/test/configCases/html/module-sibling-type/test.config.js b/test/configCases/html/module-sibling-type/test.config.js new file mode 100644 index 00000000000..07b84041615 --- /dev/null +++ b/test/configCases/html/module-sibling-type/test.config.js @@ -0,0 +1,7 @@ +"use strict"; + +module.exports = { + findBundle() { + return ["./main.mjs"]; + } +}; diff --git a/test/configCases/html/module-sibling-type/webpack.config.js b/test/configCases/html/module-sibling-type/webpack.config.js new file mode 100644 index 00000000000..113fa768a3a --- /dev/null +++ b/test/configCases/html/module-sibling-type/webpack.config.js @@ -0,0 +1,44 @@ +"use strict"; + +// A native ` + + + + +" +`; diff --git a/test/configCases/html/modulepreload-esm/__snapshots__/ConfigTest.snap b/test/configCases/html/modulepreload-esm/__snapshots__/ConfigTest.snap new file mode 100644 index 00000000000..344bdb8a91c --- /dev/null +++ b/test/configCases/html/modulepreload-esm/__snapshots__/ConfigTest.snap @@ -0,0 +1,131 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigTestCases html modulepreload-esm exported tests should emit module-format chunks (no IIFE wrapper) when output.module is enabled 1`] = ` +"/*!********************!*\\\\ + !*** ./preload.js ***! + \\\\********************/ +const value = \\"preload module\\"; +globalThis.__preloadLoaded = true; +/* unused harmony default export */ var __WEBPACK_DEFAULT_EXPORT__ = ((/* unused pure expression or super */ null && (value))); + +" +`; + +exports[`ConfigTestCases html modulepreload-esm exported tests should emit module-format chunks (no IIFE wrapper) when output.module is enabled 2`] = ` +"/******/ var __webpack_modules__ = ({ + +/***/ 492 +/*!********************!*\\\\ + !*** ./preload.js ***! + \\\\********************/ +(__unused_webpack_module, __webpack_exports__, __webpack_require__) { + +const value = \\"preload module\\"; +globalThis.__preloadLoaded = true; +/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (value); + +/* harmony export */ __webpack_require__.d(__webpack_exports__, [ +/* harmony export */ \\"A\\", 0, /* export default binding */ __WEBPACK_DEFAULT_EXPORT__ +/* harmony export */ ]); + + +/***/ } + +/******/ }); +/************************************************************************/ +/******/ // The module cache +/******/ const __webpack_module_cache__ = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ // Check if module is in cache +/******/ const cachedModule = __webpack_module_cache__[moduleId]; +/******/ if (cachedModule !== undefined) { +/******/ return cachedModule.exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ const module = __webpack_module_cache__[moduleId] = { +/******/ // no module.id needed +/******/ // no module.loaded needed +/******/ exports: {} +/******/ }; +/******/ +/******/ // Execute the module function +/******/ if (!(moduleId in __webpack_modules__)) { +/******/ delete __webpack_module_cache__[moduleId]; +/******/ const e = new Error(\\"Cannot find module '\\" + moduleId + \\"'\\"); +/******/ e.code = 'MODULE_NOT_FOUND'; +/******/ throw e; +/******/ } +/******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__); +/******/ +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } +/******/ +/************************************************************************/ +/******/ /* webpack/runtime/define property getters */ +/******/ (() => { +/******/ // define getter/value functions for harmony exports +/******/ __webpack_require__.d = (exports, definition) => { +/******/ if(Array.isArray(definition)) { +/******/ var i = 0; +/******/ while(i < definition.length) { +/******/ var key = definition[i++]; +/******/ var binding = definition[i++]; +/******/ if(!__webpack_require__.o(exports, key)) { +/******/ if(binding === 0) { +/******/ Object.defineProperty(exports, key, { enumerable: true, value: definition[i++] }); +/******/ } else { +/******/ Object.defineProperty(exports, key, { enumerable: true, get: binding }); +/******/ } +/******/ } else if(binding === 0) { i++; } +/******/ } +/******/ } else { +/******/ for(var key in definition) { +/******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) { +/******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] }); +/******/ } +/******/ } +/******/ } +/******/ }; +/******/ })(); +/******/ +/******/ /* webpack/runtime/hasOwnProperty shorthand */ +/******/ (() => { +/******/ __webpack_require__.o = (obj, prop) => (Object.hasOwn(obj, prop)) +/******/ })(); +/******/ +/************************************************************************/ +let __webpack_exports__ = {}; +/*!******************!*\\\\ + !*** ./entry.js ***! + \\\\******************/ +/* harmony import */ var _preload_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./preload.js */ 492); + + +globalThis.__entryResult = \`entry: \${_preload_js__WEBPACK_IMPORTED_MODULE_0__/* [\\"default\\"] */ .A}\`; + +" +`; + +exports[`ConfigTestCases html modulepreload-esm exported tests should rewrite and + + + + +" +`; diff --git a/test/configCases/html/modulepreload-esm/entry.js b/test/configCases/html/modulepreload-esm/entry.js new file mode 100644 index 00000000000..bf063ad406f --- /dev/null +++ b/test/configCases/html/modulepreload-esm/entry.js @@ -0,0 +1,3 @@ +import preload from "./preload.js"; + +globalThis.__entryResult = `entry: ${preload}`; diff --git a/test/configCases/html/modulepreload-esm/index.js b/test/configCases/html/modulepreload-esm/index.js new file mode 100644 index 00000000000..23b0020fa11 --- /dev/null +++ b/test/configCases/html/modulepreload-esm/index.js @@ -0,0 +1,71 @@ +import fs from "fs"; +import path from "path"; +import { fileURLToPath } from "url"; + +import page from "./page.html"; + +const here = path.dirname(fileURLToPath(import.meta.url)); + +const readChunk = (name) => fs.readFileSync(path.resolve(here, name), "utf-8"); + +const chunkFor = (regex) => { + const m = page.match(regex); + if (!m) throw new Error(`No chunk match for ${regex}`); + return m[1]; +}; + +it("should rewrite and + + + + diff --git a/test/configCases/html/modulepreload-esm/preload-other.js b/test/configCases/html/modulepreload-esm/preload-other.js new file mode 100644 index 00000000000..8f32e3cbab8 --- /dev/null +++ b/test/configCases/html/modulepreload-esm/preload-other.js @@ -0,0 +1,2 @@ +globalThis.__preloadOtherLoaded = true; +export default "preload-other module"; diff --git a/test/configCases/html/modulepreload-esm/preload.js b/test/configCases/html/modulepreload-esm/preload.js new file mode 100644 index 00000000000..ccc9bde06a5 --- /dev/null +++ b/test/configCases/html/modulepreload-esm/preload.js @@ -0,0 +1,3 @@ +const value = "preload module"; +globalThis.__preloadLoaded = true; +export default value; diff --git a/test/configCases/html/modulepreload-esm/test.config.js b/test/configCases/html/modulepreload-esm/test.config.js new file mode 100644 index 00000000000..07b84041615 --- /dev/null +++ b/test/configCases/html/modulepreload-esm/test.config.js @@ -0,0 +1,7 @@ +"use strict"; + +module.exports = { + findBundle() { + return ["./main.mjs"]; + } +}; diff --git a/test/configCases/html/modulepreload-esm/test.filter.js b/test/configCases/html/modulepreload-esm/test.filter.js new file mode 100644 index 00000000000..f0f143b8e02 --- /dev/null +++ b/test/configCases/html/modulepreload-esm/test.filter.js @@ -0,0 +1,9 @@ +"use strict"; + +// Tests use `import.meta.url` to locate emitted chunks at runtime and +// `globalThis` in fixtures, both of which require Node 12+. +module.exports = function filter() { + const major = Number(process.versions.node.split(".")[0]); + // the es2022 snapshot uses `Object.hasOwn`, available since Node 16.9 + return major >= 12 && typeof Object.hasOwn === "function"; +}; diff --git a/test/configCases/html/modulepreload-esm/webpack.config.js b/test/configCases/html/modulepreload-esm/webpack.config.js new file mode 100644 index 00000000000..dad1657187d --- /dev/null +++ b/test/configCases/html/modulepreload-esm/webpack.config.js @@ -0,0 +1,32 @@ +"use strict"; + +/** @type {import("../../../../").Configuration} */ +module.exports = { + target: ["web", "es2022"], + node: { + __dirname: false, + __filename: false + }, + externalsPresets: { + node: true + }, + module: { + parser: { + javascript: { + importMeta: false + } + } + }, + output: { + filename: "[name].mjs", + chunkFilename: "[name].chunk.mjs", + module: true + }, + optimization: { + chunkIds: "named" + }, + experiments: { + html: true, + outputModule: true + } +}; diff --git a/test/configCases/html/null-char-parse/__snapshots__/ConfigCacheTest.snap b/test/configCases/html/null-char-parse/__snapshots__/ConfigCacheTest.snap new file mode 100644 index 00000000000..dd5a44e9681 Binary files /dev/null and b/test/configCases/html/null-char-parse/__snapshots__/ConfigCacheTest.snap differ diff --git a/test/configCases/html/null-char-parse/__snapshots__/ConfigTest.snap b/test/configCases/html/null-char-parse/__snapshots__/ConfigTest.snap new file mode 100644 index 00000000000..fd6931f91ef Binary files /dev/null and b/test/configCases/html/null-char-parse/__snapshots__/ConfigTest.snap differ diff --git a/test/configCases/html/null-char-parse/index.js b/test/configCases/html/null-char-parse/index.js new file mode 100644 index 00000000000..d91908eb58b --- /dev/null +++ b/test/configCases/html/null-char-parse/index.js @@ -0,0 +1,5 @@ +import page from "./page.html"; + +it("should compile HTML containing null characters", () => { + expect(page).toMatchSnapshot(); +}); diff --git a/test/configCases/html/null-char-parse/page.html b/test/configCases/html/null-char-parse/page.html new file mode 100644 index 00000000000..3a768fb7917 Binary files /dev/null and b/test/configCases/html/null-char-parse/page.html differ diff --git a/test/configCases/html/null-char-parse/webpack.config.js b/test/configCases/html/null-char-parse/webpack.config.js new file mode 100644 index 00000000000..bc4cbc8fe56 --- /dev/null +++ b/test/configCases/html/null-char-parse/webpack.config.js @@ -0,0 +1,9 @@ +"use strict"; + +/** @type {import("../../../../").Configuration} */ +module.exports = { + target: "web", + experiments: { + html: true + } +}; diff --git a/test/configCases/html/output-html-crossorigin-template/src/a.js b/test/configCases/html/output-html-crossorigin-template/src/a.js new file mode 100644 index 00000000000..7b2a3460115 --- /dev/null +++ b/test/configCases/html/output-html-crossorigin-template/src/a.js @@ -0,0 +1 @@ +console.log("a"); diff --git a/test/configCases/html/output-html-crossorigin-template/src/b.js b/test/configCases/html/output-html-crossorigin-template/src/b.js new file mode 100644 index 00000000000..6d012e7f1f1 --- /dev/null +++ b/test/configCases/html/output-html-crossorigin-template/src/b.js @@ -0,0 +1 @@ +console.log("b"); diff --git a/test/configCases/html/output-html-crossorigin-template/src/page.html b/test/configCases/html/output-html-crossorigin-template/src/page.html new file mode 100644 index 00000000000..774d4497c48 --- /dev/null +++ b/test/configCases/html/output-html-crossorigin-template/src/page.html @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/test/configCases/html/output-html-crossorigin-template/test.config.js b/test/configCases/html/output-html-crossorigin-template/test.config.js new file mode 100644 index 00000000000..1f8d2c48be8 --- /dev/null +++ b/test/configCases/html/output-html-crossorigin-template/test.config.js @@ -0,0 +1,7 @@ +"use strict"; + +module.exports = { + findBundle() { + return ["./test.js"]; + } +}; diff --git a/test/configCases/html/output-html-crossorigin-template/test.js b/test/configCases/html/output-html-crossorigin-template/test.js new file mode 100644 index 00000000000..129e119a87e --- /dev/null +++ b/test/configCases/html/output-html-crossorigin-template/test.js @@ -0,0 +1,17 @@ +const fs = require("fs"); +const path = require("path"); + +const read = (file) => fs.readFileSync(path.resolve(__dirname, file), "utf-8"); +const count = (str, sub) => str.split(sub).length - 1; + +it("adds output.crossOriginLoading to a template tag without crossorigin", () => { + const html = read("page.html"); + // the `a.js` tag had no crossorigin -> exactly one tag gets the default + expect(count(html, 'crossorigin="anonymous"')).toBe(1); +}); + +it("preserves an author-set crossorigin value on a template tag", () => { + const html = read("page.html"); + // the `b.js` tag's value is kept, not duplicated or overridden + expect(count(html, 'crossorigin="use-credentials"')).toBe(1); +}); diff --git a/test/configCases/html/output-html-crossorigin-template/webpack.config.js b/test/configCases/html/output-html-crossorigin-template/webpack.config.js new file mode 100644 index 00000000000..b11a11f0580 --- /dev/null +++ b/test/configCases/html/output-html-crossorigin-template/webpack.config.js @@ -0,0 +1,39 @@ +"use strict"; + +const fs = require("fs"); +const path = require("path"); +const webpack = require("../../../../"); + +/** @type {import("../../../../").WebpackPluginInstance} */ +const copyTest = { + apply(compiler) { + compiler.hooks.compilation.tap("Test", (compilation) => { + compilation.hooks.processAssets.tap( + { + name: "copy-test", + stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL + }, + () => { + compilation.emitAsset( + "test.js", + new webpack.sources.RawSource( + fs.readFileSync(path.resolve(__dirname, "test.js")) + ) + ); + } + ); + }); + } +}; + +// Real `.html` template entry: `output.crossOriginLoading` is added to tags +// that have no `crossorigin`, but an author-set value is preserved. +/** @type {import("../../../../").Configuration} */ +module.exports = { + target: "web", + entry: { page: "./src/page.html" }, + output: { filename: "[name].js", crossOriginLoading: "anonymous" }, + optimization: { minimize: false }, + experiments: { html: true }, + plugins: [copyTest] +}; diff --git a/test/configCases/html/output-html-crossorigin/src/main.js b/test/configCases/html/output-html-crossorigin/src/main.js new file mode 100644 index 00000000000..78092c48fee --- /dev/null +++ b/test/configCases/html/output-html-crossorigin/src/main.js @@ -0,0 +1,3 @@ +import "./style.css"; + +console.log("crossorigin"); diff --git a/test/configCases/html/output-html-crossorigin/src/style.css b/test/configCases/html/output-html-crossorigin/src/style.css new file mode 100644 index 00000000000..461cdec8fb1 --- /dev/null +++ b/test/configCases/html/output-html-crossorigin/src/style.css @@ -0,0 +1,3 @@ +.a { + color: red; +} diff --git a/test/configCases/html/output-html-crossorigin/test.config.js b/test/configCases/html/output-html-crossorigin/test.config.js new file mode 100644 index 00000000000..1f8d2c48be8 --- /dev/null +++ b/test/configCases/html/output-html-crossorigin/test.config.js @@ -0,0 +1,7 @@ +"use strict"; + +module.exports = { + findBundle() { + return ["./test.js"]; + } +}; diff --git a/test/configCases/html/output-html-crossorigin/test.js b/test/configCases/html/output-html-crossorigin/test.js new file mode 100644 index 00000000000..73e2085b037 --- /dev/null +++ b/test/configCases/html/output-html-crossorigin/test.js @@ -0,0 +1,13 @@ +const fs = require("fs"); +const path = require("path"); + +const read = (file) => fs.readFileSync(path.resolve(__dirname, file), "utf-8"); +const count = (str, sub) => str.split(sub).length - 1; + +it("mirrors output.crossOriginLoading onto the injected script and stylesheet", () => { + const html = read("main.html"); + expect(html).toMatch(/src="[^"]+\.js"/); + expect(html).toMatch(/href="[^"]+\.css"/); + // both the script and the extracted stylesheet carry the attribute + expect(count(html, 'crossorigin="anonymous"')).toBe(2); +}); diff --git a/test/configCases/html/output-html-crossorigin/webpack.config.js b/test/configCases/html/output-html-crossorigin/webpack.config.js new file mode 100644 index 00000000000..67e0e647ee3 --- /dev/null +++ b/test/configCases/html/output-html-crossorigin/webpack.config.js @@ -0,0 +1,43 @@ +"use strict"; + +const fs = require("fs"); +const path = require("path"); +const webpack = require("../../../../"); + +/** @type {import("../../../../").WebpackPluginInstance} */ +const copyTest = { + apply(compiler) { + compiler.hooks.compilation.tap("Test", (compilation) => { + compilation.hooks.processAssets.tap( + { + name: "copy-test", + stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL + }, + () => { + compilation.emitAsset( + "test.js", + new webpack.sources.RawSource( + fs.readFileSync(path.resolve(__dirname, "test.js")) + ) + ); + } + ); + }); + } +}; + +// `output.crossOriginLoading` must be mirrored onto the injected JS script and +// the CSS `` extracted from the JS import. +/** @type {import("../../../../").Configuration} */ +module.exports = { + target: "web", + entry: { main: "./src/main.js" }, + output: { + filename: "[name].js", + html: true, + crossOriginLoading: "anonymous" + }, + optimization: { minimize: false }, + experiments: { html: true, css: true }, + plugins: [copyTest] +}; diff --git a/test/configCases/html/output-html-css/src/styles.css b/test/configCases/html/output-html-css/src/styles.css new file mode 100644 index 00000000000..e3fc0241a1a --- /dev/null +++ b/test/configCases/html/output-html-css/src/styles.css @@ -0,0 +1,3 @@ +.x { + color: red; +} diff --git a/test/configCases/html/output-html-css/test.config.js b/test/configCases/html/output-html-css/test.config.js new file mode 100644 index 00000000000..1f8d2c48be8 --- /dev/null +++ b/test/configCases/html/output-html-css/test.config.js @@ -0,0 +1,7 @@ +"use strict"; + +module.exports = { + findBundle() { + return ["./test.js"]; + } +}; diff --git a/test/configCases/html/output-html-css/test.js b/test/configCases/html/output-html-css/test.js new file mode 100644 index 00000000000..125201b3100 --- /dev/null +++ b/test/configCases/html/output-html-css/test.js @@ -0,0 +1,8 @@ +const fs = require("fs"); +const path = require("path"); + +it("wraps a CSS entry with only a stylesheet link, no script", () => { + const html = fs.readFileSync(path.resolve(__dirname, "styles.html"), "utf-8"); + expect(html).toMatch(//); + expect(html).not.toMatch(/__html_[^"]*\.js/); +}); diff --git a/test/configCases/html/output-html-css/webpack.config.js b/test/configCases/html/output-html-css/webpack.config.js new file mode 100644 index 00000000000..ec45d166a24 --- /dev/null +++ b/test/configCases/html/output-html-css/webpack.config.js @@ -0,0 +1,36 @@ +"use strict"; + +const fs = require("fs"); +const path = require("path"); +const webpack = require("../../../../"); + +/** @type {import("../../../../").WebpackPluginInstance} */ +const copyTest = { + apply(compiler) { + compiler.hooks.compilation.tap("Test", (compilation) => { + compilation.hooks.processAssets.tap( + { + name: "copy-test", + stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL + }, + () => { + compilation.emitAsset( + "test.js", + new webpack.sources.RawSource( + fs.readFileSync(path.resolve(__dirname, "test.js")) + ) + ); + } + ); + }); + } +}; + +/** @type {import("../../../../").Configuration} */ +module.exports = { + target: "web", + entry: { styles: "./src/styles.css" }, + output: { filename: "[name].js", html: true }, + experiments: { html: true, css: true }, + plugins: [copyTest] +}; diff --git a/test/configCases/html/output-html-depend-on-css/src/app.js b/test/configCases/html/output-html-depend-on-css/src/app.js new file mode 100644 index 00000000000..d24e2916dfa --- /dev/null +++ b/test/configCases/html/output-html-depend-on-css/src/app.js @@ -0,0 +1,3 @@ +import { v } from "./shared.js"; + +(self.order = self.order || []).push(`APP:${v}`); diff --git a/test/configCases/html/output-html-depend-on-css/src/shared.js b/test/configCases/html/output-html-depend-on-css/src/shared.js new file mode 100644 index 00000000000..d7d2f72175a --- /dev/null +++ b/test/configCases/html/output-html-depend-on-css/src/shared.js @@ -0,0 +1,5 @@ +import "./theme.css"; + +(self.order = self.order || []).push("SHARED"); + +export const v = "v"; diff --git a/test/configCases/html/output-html-depend-on-css/src/theme.css b/test/configCases/html/output-html-depend-on-css/src/theme.css new file mode 100644 index 00000000000..ab95c8d8cad --- /dev/null +++ b/test/configCases/html/output-html-depend-on-css/src/theme.css @@ -0,0 +1,3 @@ +.theme { + color: red; +} diff --git a/test/configCases/html/output-html-depend-on-css/test.config.js b/test/configCases/html/output-html-depend-on-css/test.config.js new file mode 100644 index 00000000000..1f8d2c48be8 --- /dev/null +++ b/test/configCases/html/output-html-depend-on-css/test.config.js @@ -0,0 +1,7 @@ +"use strict"; + +module.exports = { + findBundle() { + return ["./test.js"]; + } +}; diff --git a/test/configCases/html/output-html-depend-on-css/test.js b/test/configCases/html/output-html-depend-on-css/test.js new file mode 100644 index 00000000000..9699f6cd340 --- /dev/null +++ b/test/configCases/html/output-html-depend-on-css/test.js @@ -0,0 +1,18 @@ +const fs = require("fs"); +const path = require("path"); + +const read = (file) => fs.readFileSync(path.resolve(__dirname, file), "utf-8"); + +it("injects the dependOn target's stylesheet into the dependant page", () => { + const html = read("app.html"); + expect(html).toMatch(//); +}); + +it("loads the stylesheet before the script that needs it", () => { + const html = read("app.html"); + const linkIndex = html.indexOf('rel="stylesheet"'); + const scriptIndex = html.indexOf(" { + compilation.hooks.processAssets.tap( + { + name: "copy-test", + stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL + }, + () => { + compilation.emitAsset( + "test.js", + new webpack.sources.RawSource( + fs.readFileSync(path.resolve(__dirname, "test.js")) + ) + ); + } + ); + }); + } +}; + +// The dependOn target (`shared`) pulls in CSS — the dependant page must inject +// that stylesheet too, before its own script. +/** @type {import("../../../../").Configuration} */ +module.exports = { + target: "web", + entry: { + shared: "./src/shared.js", + app: { import: "./src/app.js", dependOn: "shared" } + }, + output: { filename: "[name].js", html: true }, + optimization: { minimize: false }, + experiments: { html: true, css: true }, + plugins: [copyTest] +}; diff --git a/test/configCases/html/output-html-depend-on-transitive/src/app.js b/test/configCases/html/output-html-depend-on-transitive/src/app.js new file mode 100644 index 00000000000..b62f06b2287 --- /dev/null +++ b/test/configCases/html/output-html-depend-on-transitive/src/app.js @@ -0,0 +1,4 @@ +import { mid1 } from "./mid1.js"; +import { mid2 } from "./mid2.js"; + +(self.order = self.order || []).push(`APP:${mid1}${mid2}`); diff --git a/test/configCases/html/output-html-depend-on-transitive/src/base.js b/test/configCases/html/output-html-depend-on-transitive/src/base.js new file mode 100644 index 00000000000..ac6fcee1596 --- /dev/null +++ b/test/configCases/html/output-html-depend-on-transitive/src/base.js @@ -0,0 +1,3 @@ +(self.order = self.order || []).push("BASE"); + +export const base = "B"; diff --git a/test/configCases/html/output-html-depend-on-transitive/src/mid1.js b/test/configCases/html/output-html-depend-on-transitive/src/mid1.js new file mode 100644 index 00000000000..f23a95cd919 --- /dev/null +++ b/test/configCases/html/output-html-depend-on-transitive/src/mid1.js @@ -0,0 +1,5 @@ +import { base } from "./base.js"; + +(self.order = self.order || []).push(`MID1:${base}`); + +export const mid1 = "M1"; diff --git a/test/configCases/html/output-html-depend-on-transitive/src/mid2.js b/test/configCases/html/output-html-depend-on-transitive/src/mid2.js new file mode 100644 index 00000000000..4ef88741c77 --- /dev/null +++ b/test/configCases/html/output-html-depend-on-transitive/src/mid2.js @@ -0,0 +1,5 @@ +import { base } from "./base.js"; + +(self.order = self.order || []).push(`MID2:${base}`); + +export const mid2 = "M2"; diff --git a/test/configCases/html/output-html-depend-on-transitive/test.config.js b/test/configCases/html/output-html-depend-on-transitive/test.config.js new file mode 100644 index 00000000000..1f8d2c48be8 --- /dev/null +++ b/test/configCases/html/output-html-depend-on-transitive/test.config.js @@ -0,0 +1,7 @@ +"use strict"; + +module.exports = { + findBundle() { + return ["./test.js"]; + } +}; diff --git a/test/configCases/html/output-html-depend-on-transitive/test.js b/test/configCases/html/output-html-depend-on-transitive/test.js new file mode 100644 index 00000000000..34c93132efc --- /dev/null +++ b/test/configCases/html/output-html-depend-on-transitive/test.js @@ -0,0 +1,37 @@ +const fs = require("fs"); +const path = require("path"); +const vm = require("vm"); + +const read = (file) => fs.readFileSync(path.resolve(__dirname, file), "utf-8"); +// Match the `src` attribute rather than the whole tag — the page only puts +// `src` on ``) + .join(""); + return `data:text/html,

From plugin

${tags}`; + } + ); + } + }, + { + apply(compiler) { + compiler.hooks.compilation.tap("Test", (compilation) => { + compilation.hooks.processAssets.tap( + { + name: "copy-test", + stage: + compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL + }, + () => { + const data = fs.readFileSync(path.resolve(__dirname, "test.js")); + compilation.emitAsset( + "test.js", + new webpack.sources.RawSource(data) + ); + } + ); + }); + } + } + ] +}; diff --git a/test/configCases/html/output-html-per-entry/src/a.js b/test/configCases/html/output-html-per-entry/src/a.js new file mode 100644 index 00000000000..adaeba36533 --- /dev/null +++ b/test/configCases/html/output-html-per-entry/src/a.js @@ -0,0 +1 @@ +console.log('a') diff --git a/test/configCases/html/output-html-per-entry/src/b.js b/test/configCases/html/output-html-per-entry/src/b.js new file mode 100644 index 00000000000..6a919339d27 --- /dev/null +++ b/test/configCases/html/output-html-per-entry/src/b.js @@ -0,0 +1 @@ +console.log('b') diff --git a/test/configCases/html/output-html-per-entry/test.config.js b/test/configCases/html/output-html-per-entry/test.config.js new file mode 100644 index 00000000000..1f8d2c48be8 --- /dev/null +++ b/test/configCases/html/output-html-per-entry/test.config.js @@ -0,0 +1,7 @@ +"use strict"; + +module.exports = { + findBundle() { + return ["./test.js"]; + } +}; diff --git a/test/configCases/html/output-html-per-entry/test.js b/test/configCases/html/output-html-per-entry/test.js new file mode 100644 index 00000000000..453485a0f68 --- /dev/null +++ b/test/configCases/html/output-html-per-entry/test.js @@ -0,0 +1,7 @@ +const fs = require("fs"); +const path = require("path"); + +it("respects per-entry html:false override of output.html:true", () => { + expect(fs.existsSync(path.resolve(__dirname, "a.html"))).toBe(true); + expect(fs.existsSync(path.resolve(__dirname, "b.html"))).toBe(false); +}); diff --git a/test/configCases/html/output-html-per-entry/webpack.config.js b/test/configCases/html/output-html-per-entry/webpack.config.js new file mode 100644 index 00000000000..7b16cc8a4b1 --- /dev/null +++ b/test/configCases/html/output-html-per-entry/webpack.config.js @@ -0,0 +1,38 @@ +"use strict"; + +const fs = require("fs"); +const path = require("path"); +const webpack = require("../../../../"); + +/** @type {import("../../../../").WebpackPluginInstance} */ +const copyTest = { + apply(compiler) { + compiler.hooks.compilation.tap("Test", (compilation) => { + compilation.hooks.processAssets.tap( + { + name: "copy-test", + stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL + }, + () => { + compilation.emitAsset( + "test.js", + new webpack.sources.RawSource( + fs.readFileSync(path.resolve(__dirname, "test.js")) + ) + ); + } + ); + }); + } +}; + +/** @type {import("../../../../").Configuration} */ +module.exports = { + entry: { + a: "./src/a.js", + b: { import: "./src/b.js", html: false } + }, + output: { filename: "[name].js", html: true }, + experiments: { html: true }, + plugins: [copyTest] +}; diff --git a/test/configCases/html/output-html-script-loading/src/main.js b/test/configCases/html/output-html-script-loading/src/main.js new file mode 100644 index 00000000000..aef22247d75 --- /dev/null +++ b/test/configCases/html/output-html-script-loading/src/main.js @@ -0,0 +1 @@ +export default 1; diff --git a/test/configCases/html/output-html-script-loading/test.config.js b/test/configCases/html/output-html-script-loading/test.config.js new file mode 100644 index 00000000000..1f8d2c48be8 --- /dev/null +++ b/test/configCases/html/output-html-script-loading/test.config.js @@ -0,0 +1,7 @@ +"use strict"; + +module.exports = { + findBundle() { + return ["./test.js"]; + } +}; diff --git a/test/configCases/html/output-html-script-loading/test.js b/test/configCases/html/output-html-script-loading/test.js new file mode 100644 index 00000000000..10d303fa8a3 --- /dev/null +++ b/test/configCases/html/output-html-script-loading/test.js @@ -0,0 +1,30 @@ +const fs = require("fs"); +const path = require("path"); + +const read = (name) => + fs.readFileSync(path.resolve(__dirname, name), "utf-8"); + +it("scriptLoading: defer emits a deferred script", () => { + expect(read("defer.html")).toMatch(/` bug. + expect((page.match(/]*><\/script>/); +}); + +it("should emit a real `type=module` ` would produce invalid, +// non-executing markup like ``. + +/** @type {import("../../../../").Configuration} */ +module.exports = { + target: "web", + output: { + filename: "[name].js", + chunkFilename: "[name].chunk.js" + }, + optimization: { + chunkIds: "named", + runtimeChunk: { + name: (entrypoint) => + entrypoint.name.startsWith("__html_") + ? `${entrypoint.name}-runtime` + : undefined + } + }, + module: { + parser: { + html: { + sources: [ + "...", + { tag: "my-script", attribute: "src", type: "script" }, + { tag: "my-module", attribute: "src", type: "script-module" } + ] + } + } + }, + experiments: { + html: true + } +}; diff --git a/test/configCases/html/parser-sources-custom/hero.png b/test/configCases/html/parser-sources-custom/hero.png new file mode 100644 index 00000000000..0dd1608e45a --- /dev/null +++ b/test/configCases/html/parser-sources-custom/hero.png @@ -0,0 +1,2 @@ +PNG + diff --git a/test/configCases/html/parser-sources-custom/index.js b/test/configCases/html/parser-sources-custom/index.js new file mode 100644 index 00000000000..0d846ac5b53 --- /dev/null +++ b/test/configCases/html/parser-sources-custom/index.js @@ -0,0 +1,30 @@ +import page from "./page.html"; + +it("should rewrite custom data-src URLs the same way as default sources", () => { + // Default `` still rewritten. + expect(page).not.toContain('src="./hero.png"'); + expect(page).toMatch(/src="hero\.png"/); + // Custom `data-src` rewritten via the user-supplied source entry. + expect(page).not.toContain('data-src="./lazy.png"'); + expect(page).toMatch(/data-src="lazy\.png"/); +}); + +it("should parse custom srcset entries as srcset (not src)", () => { + expect(page).not.toContain('data-srcset="./small.png 1x, ./large.png 2x"'); + // Both candidates rewritten to bare filenames; descriptors preserved. + expect(page).toMatch(/data-srcset="small\.png 1x,\s*large\.png 2x"/); +}); + +it("should match a tagless entry against any element", () => { + expect(page).not.toContain('data-href="./linked.png"'); + expect(page).toMatch(/
/); +}); + +it("should not promote custom sources into compilation entries", () => { + // Even if a user adds `{ tag: 'script', attribute: 'src', type: 'src' }` + // without `'...'`, it should be a plain URL rewrite — never a chunk + // entry. That's verified here by the absence of `__html_*` chunk + // names in the rewritten HTML; the custom-source `data-src` / + // `data-srcset` / `data-href` URLs are asset URLs, not script chunks. + expect(page).not.toMatch(/__html_[a-f0-9]+_\d+/); +}); diff --git a/test/configCases/html/parser-sources-custom/large.png b/test/configCases/html/parser-sources-custom/large.png new file mode 100644 index 00000000000..0dd1608e45a --- /dev/null +++ b/test/configCases/html/parser-sources-custom/large.png @@ -0,0 +1,2 @@ +PNG + diff --git a/test/configCases/html/parser-sources-custom/lazy.png b/test/configCases/html/parser-sources-custom/lazy.png new file mode 100644 index 00000000000..0dd1608e45a --- /dev/null +++ b/test/configCases/html/parser-sources-custom/lazy.png @@ -0,0 +1,2 @@ +PNG + diff --git a/test/configCases/html/parser-sources-custom/linked.png b/test/configCases/html/parser-sources-custom/linked.png new file mode 100644 index 00000000000..0dd1608e45a --- /dev/null +++ b/test/configCases/html/parser-sources-custom/linked.png @@ -0,0 +1,2 @@ +PNG + diff --git a/test/configCases/html/parser-sources-custom/page.html b/test/configCases/html/parser-sources-custom/page.html new file mode 100644 index 00000000000..714d348061b --- /dev/null +++ b/test/configCases/html/parser-sources-custom/page.html @@ -0,0 +1,11 @@ + + + +Custom sources + + +hero +responsive +
content
+ + diff --git a/test/configCases/html/parser-sources-custom/small.png b/test/configCases/html/parser-sources-custom/small.png new file mode 100644 index 00000000000..0dd1608e45a --- /dev/null +++ b/test/configCases/html/parser-sources-custom/small.png @@ -0,0 +1,2 @@ +PNG + diff --git a/test/configCases/html/parser-sources-custom/test.config.js b/test/configCases/html/parser-sources-custom/test.config.js new file mode 100644 index 00000000000..0fa717e15cd --- /dev/null +++ b/test/configCases/html/parser-sources-custom/test.config.js @@ -0,0 +1,12 @@ +"use strict"; + +const fs = require("fs"); + +// `output.filename` is `[name].js` so the test entry bundle lives at +// `main.js`, not `bundle0.js`. Tell the harness where to find it. +module.exports = { + findBundle(_i, options) { + const files = fs.readdirSync(options.output.path); + return files.includes("main.js") ? ["./main.js"] : undefined; + } +}; diff --git a/test/configCases/html/parser-sources-custom/webpack.config.js b/test/configCases/html/parser-sources-custom/webpack.config.js new file mode 100644 index 00000000000..c4f95656a00 --- /dev/null +++ b/test/configCases/html/parser-sources-custom/webpack.config.js @@ -0,0 +1,26 @@ +"use strict"; + +/** @type {import("../../../../").Configuration} */ +module.exports = { + target: "web", + output: { + filename: "[name].js", + assetModuleFilename: "[name][ext]" + }, + module: { + parser: { + html: { + sources: [ + "...", + { tag: "img", attribute: "data-src", type: "src" }, + { tag: "img", attribute: "data-srcset", type: "srcset" }, + // Omit `tag` to match any element. + { attribute: "data-href", type: "src" } + ] + } + } + }, + experiments: { + html: true + } +}; diff --git a/test/configCases/html/parser-sources-disabled/index.js b/test/configCases/html/parser-sources-disabled/index.js new file mode 100644 index 00000000000..3f8aa70131b --- /dev/null +++ b/test/configCases/html/parser-sources-disabled/index.js @@ -0,0 +1,29 @@ +import page from "./page.html"; + +it("should leave URL attributes untouched when module.parser.html.sources is false", () => { + // None of the referenced files (./entry.js, ./styles.css, ./icon.png, + // ./image.png) exist in this directory — disabling `sources` means + // the parser must not turn them into webpack dependencies, otherwise + // the compilation would fail with module-not-found errors. + expect(page).toContain('href="./icon.png"'); + expect(page).toContain('href="./styles.css"'); + expect(page).toContain('src="./entry.js"'); + expect(page).toContain('src="./image.png"'); +}); + +it("should not let Object.prototype-named tags bypass sources:false", () => { + // `` must stay untouched — the + // lookup tables are null-prototype, so `constructor` doesn't resolve + // to an inherited value and never becomes a chunk entry (the file + // doesn't exist; a bogus entry would fail the build). + expect(page).toContain('name="./proto-bypass.js"'); + expect(page).not.toMatch(/__html_[a-f0-9]+_\d+/); +}); + +it("should still bundle inline + + + +image + + + + diff --git a/test/configCases/html/parser-sources-disabled/webpack.config.js b/test/configCases/html/parser-sources-disabled/webpack.config.js new file mode 100644 index 00000000000..7f70fa9a577 --- /dev/null +++ b/test/configCases/html/parser-sources-disabled/webpack.config.js @@ -0,0 +1,16 @@ +"use strict"; + +/** @type {import("../../../../").Configuration} */ +module.exports = { + target: "web", + module: { + parser: { + html: { + sources: false + } + } + }, + experiments: { + html: true + } +}; diff --git a/test/configCases/html/parser-sources-filter/__snapshots__/ConfigCacheTest.snap b/test/configCases/html/parser-sources-filter/__snapshots__/ConfigCacheTest.snap new file mode 100644 index 00000000000..516bc53f375 --- /dev/null +++ b/test/configCases/html/parser-sources-filter/__snapshots__/ConfigCacheTest.snap @@ -0,0 +1,27 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigCacheTestCases html parser-sources-filter exported tests should apply sources[].filter to conditionally extract URLs 1`] = ` +" + +sources filter + + + +\\"hero\\" + + +\\"hero-small\\" + + +\\"hero-no-attr\\" + + + + + + + + + +" +`; diff --git a/test/configCases/html/parser-sources-filter/__snapshots__/ConfigTest.snap b/test/configCases/html/parser-sources-filter/__snapshots__/ConfigTest.snap new file mode 100644 index 00000000000..d3eb5a09086 --- /dev/null +++ b/test/configCases/html/parser-sources-filter/__snapshots__/ConfigTest.snap @@ -0,0 +1,27 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigTestCases html parser-sources-filter exported tests should apply sources[].filter to conditionally extract URLs 1`] = ` +" + +sources filter + + + +\\"hero\\" + + +\\"hero-small\\" + + +\\"hero-no-attr\\" + + + + + + + + + +" +`; diff --git a/test/configCases/html/parser-sources-filter/image.png b/test/configCases/html/parser-sources-filter/image.png new file mode 100644 index 00000000000..91a99b94e23 Binary files /dev/null and b/test/configCases/html/parser-sources-filter/image.png differ diff --git a/test/configCases/html/parser-sources-filter/index.js b/test/configCases/html/parser-sources-filter/index.js new file mode 100644 index 00000000000..59f57dcb922 --- /dev/null +++ b/test/configCases/html/parser-sources-filter/index.js @@ -0,0 +1,5 @@ +import page from "./page.html"; + +it("should apply sources[].filter to conditionally extract URLs", () => { + expect(page).toMatchSnapshot(); +}); diff --git a/test/configCases/html/parser-sources-filter/page.html b/test/configCases/html/parser-sources-filter/page.html new file mode 100644 index 00000000000..d71766fb4ae --- /dev/null +++ b/test/configCases/html/parser-sources-filter/page.html @@ -0,0 +1,22 @@ + + +sources filter + + + +hero + + +hero-small + + +hero-no-attr + + + + + + + + + diff --git a/test/configCases/html/parser-sources-filter/webpack.config.js b/test/configCases/html/parser-sources-filter/webpack.config.js new file mode 100644 index 00000000000..3938fee968a --- /dev/null +++ b/test/configCases/html/parser-sources-filter/webpack.config.js @@ -0,0 +1,32 @@ +"use strict"; + +/** @type {import("../../../../").Configuration} */ +module.exports = { + devtool: false, + target: "web", + output: { + assetModuleFilename: "[name][ext]" + }, + module: { + parser: { + html: { + sources: [ + { + tag: "img", + attribute: "src", + type: "src", + filter: (attrs) => attrs.get("data-size") === "large" + }, + { + attribute: "href", + type: "src", + filter: (attrs) => attrs.get("rel") === "icon" + } + ] + } + } + }, + experiments: { + html: true + } +}; diff --git a/test/configCases/html/parser-sources-html-entry/__snapshots__/ConfigCacheTest.snap b/test/configCases/html/parser-sources-html-entry/__snapshots__/ConfigCacheTest.snap new file mode 100644 index 00000000000..739404497bd --- /dev/null +++ b/test/configCases/html/parser-sources-html-entry/__snapshots__/ConfigCacheTest.snap @@ -0,0 +1,13 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigCacheTestCases html parser-sources-html-entry exported tests should process the linked page through the HTML pipeline as its own page 1`] = ` +" + +About + +

About

+\\"logo\\" + + +" +`; diff --git a/test/configCases/html/parser-sources-html-entry/__snapshots__/ConfigTest.snap b/test/configCases/html/parser-sources-html-entry/__snapshots__/ConfigTest.snap new file mode 100644 index 00000000000..b58dae4e302 --- /dev/null +++ b/test/configCases/html/parser-sources-html-entry/__snapshots__/ConfigTest.snap @@ -0,0 +1,13 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigTestCases html parser-sources-html-entry exported tests should process the linked page through the HTML pipeline as its own page 1`] = ` +" + +About + +

About

+\\"logo\\" + + +" +`; diff --git a/test/configCases/html/parser-sources-html-entry/about.html b/test/configCases/html/parser-sources-html-entry/about.html new file mode 100644 index 00000000000..8262089ad5b --- /dev/null +++ b/test/configCases/html/parser-sources-html-entry/about.html @@ -0,0 +1,8 @@ + + +About + +

About

+logo + + diff --git a/test/configCases/html/parser-sources-html-entry/image.png b/test/configCases/html/parser-sources-html-entry/image.png new file mode 100644 index 00000000000..b74b839e2b8 Binary files /dev/null and b/test/configCases/html/parser-sources-html-entry/image.png differ diff --git a/test/configCases/html/parser-sources-html-entry/index.js b/test/configCases/html/parser-sources-html-entry/index.js new file mode 100644 index 00000000000..53729a05798 --- /dev/null +++ b/test/configCases/html/parser-sources-html-entry/index.js @@ -0,0 +1,23 @@ +const fs = require("fs"); +const path = require("path"); + +const page = require("./page.html"); + +const readFile = (name) => + fs.readFileSync(path.resolve(__dirname, name), "utf-8"); + +it("should bundle a `type: html` link as its own page and rewrite the href", () => { + // The custom source mapped `` to a linked HTML page entry, so the + // href now points at the linked page's emitted filename, not the source path. + expect(page).toContain('About'); + expect(page).not.toContain("./about.html"); +}); + +it("should process the linked page through the HTML pipeline as its own page", () => { + const about = readFile("about.html"); + // `about.html` was emitted as a standalone page and its own `` + // was rewritten to the bundled asset. + expect(about).toContain('logo'); + expect(about).not.toContain("./image.png"); + expect(about).toMatchSnapshot(); +}); diff --git a/test/configCases/html/parser-sources-html-entry/page.html b/test/configCases/html/parser-sources-html-entry/page.html new file mode 100644 index 00000000000..c9c44783c9a --- /dev/null +++ b/test/configCases/html/parser-sources-html-entry/page.html @@ -0,0 +1,8 @@ + + +Home + +

Home

+About + + diff --git a/test/configCases/html/parser-sources-html-entry/webpack.config.js b/test/configCases/html/parser-sources-html-entry/webpack.config.js new file mode 100644 index 00000000000..d55a7e30e76 --- /dev/null +++ b/test/configCases/html/parser-sources-html-entry/webpack.config.js @@ -0,0 +1,21 @@ +"use strict"; + +/** @type {import("../../../../").Configuration} */ +module.exports = { + output: { + assetModuleFilename: "[name][ext]" + }, + module: { + parser: { + html: { + // Treat `` like a link to another HTML page: the + // referenced file is bundled as its own emitted page and the + // href is rewritten to its output filename. `"..."` keeps defaults. + sources: ["...", { tag: "a", attribute: "href", type: "html" }] + } + } + }, + experiments: { + html: true + } +}; diff --git a/test/configCases/html/parser-sources-html-shared-assets/__snapshots__/ConfigCacheTest.snap b/test/configCases/html/parser-sources-html-shared-assets/__snapshots__/ConfigCacheTest.snap new file mode 100644 index 00000000000..54d9d56cec0 --- /dev/null +++ b/test/configCases/html/parser-sources-html-shared-assets/__snapshots__/ConfigCacheTest.snap @@ -0,0 +1,13 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigCacheTestCases html parser-sources-html-shared-assets exported tests handles a script and stylesheet shared between the two pages 1`] = ` +" + + + +

About

+ + + +" +`; diff --git a/test/configCases/html/parser-sources-html-shared-assets/__snapshots__/ConfigTest.snap b/test/configCases/html/parser-sources-html-shared-assets/__snapshots__/ConfigTest.snap new file mode 100644 index 00000000000..465c390d894 --- /dev/null +++ b/test/configCases/html/parser-sources-html-shared-assets/__snapshots__/ConfigTest.snap @@ -0,0 +1,13 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigTestCases html parser-sources-html-shared-assets exported tests handles a script and stylesheet shared between the two pages 1`] = ` +" + + + +

About

+ + + +" +`; diff --git a/test/configCases/html/parser-sources-html-shared-assets/about.html b/test/configCases/html/parser-sources-html-shared-assets/about.html new file mode 100644 index 00000000000..7f5933c29ba --- /dev/null +++ b/test/configCases/html/parser-sources-html-shared-assets/about.html @@ -0,0 +1,8 @@ + + + + +

About

+ + + diff --git a/test/configCases/html/parser-sources-html-shared-assets/home.html b/test/configCases/html/parser-sources-html-shared-assets/home.html new file mode 100644 index 00000000000..3ec5a4069b6 --- /dev/null +++ b/test/configCases/html/parser-sources-html-shared-assets/home.html @@ -0,0 +1,9 @@ + + + + +

Home

+
About + + + diff --git a/test/configCases/html/parser-sources-html-shared-assets/index.js b/test/configCases/html/parser-sources-html-shared-assets/index.js new file mode 100644 index 00000000000..6c7d79baa95 --- /dev/null +++ b/test/configCases/html/parser-sources-html-shared-assets/index.js @@ -0,0 +1,28 @@ +const fs = require("fs"); +const path = require("path"); + +const home = require("./home.html"); + +const readFile = (name) => + fs.readFileSync(path.resolve(__dirname, name), "utf-8"); +const exists = (name) => fs.existsSync(path.resolve(__dirname, name)); + +it("links a second page through a `type: html` href", () => { + expect(home).toContain('About'); +}); + +it("handles a script and stylesheet shared between the two pages", () => { + const about = readFile("about.html"); + // Both pages are processed as their own page; the script and stylesheet + // they share are each emitted and referenced from both pages, with no + // entry-name collision and no dangling reference. + for (const page of [home, about]) { + const js = page.match(/]*src="([^"]+\.js)"/); + const css = page.match(//); + expect(js).not.toBeNull(); + expect(css).not.toBeNull(); + expect(exists(js[1])).toBe(true); + expect(exists(css[1])).toBe(true); + } + expect(about).toMatchSnapshot(); +}); diff --git a/test/configCases/html/parser-sources-html-shared-assets/shared.css b/test/configCases/html/parser-sources-html-shared-assets/shared.css new file mode 100644 index 00000000000..575d19f7b0e --- /dev/null +++ b/test/configCases/html/parser-sources-html-shared-assets/shared.css @@ -0,0 +1,3 @@ +body { + color: red; +} diff --git a/test/configCases/html/parser-sources-html-shared-assets/shared.js b/test/configCases/html/parser-sources-html-shared-assets/shared.js new file mode 100644 index 00000000000..4f4cfb5cf1b --- /dev/null +++ b/test/configCases/html/parser-sources-html-shared-assets/shared.js @@ -0,0 +1,2 @@ +const shared = "shared"; +export default shared; diff --git a/test/configCases/html/parser-sources-html-shared-assets/webpack.config.js b/test/configCases/html/parser-sources-html-shared-assets/webpack.config.js new file mode 100644 index 00000000000..f523c7605e6 --- /dev/null +++ b/test/configCases/html/parser-sources-html-shared-assets/webpack.config.js @@ -0,0 +1,26 @@ +"use strict"; + +/** @type {import("../../../../").Configuration} */ +module.exports = { + output: { + assetModuleFilename: "[name][ext]" + }, + module: { + generator: { + // Emit `.css` files under the default (node) target so the test can + // read the emitted pages from disk with `fs`. + css: { + exportsOnly: false + } + }, + parser: { + html: { + sources: ["...", { tag: "a", attribute: "href", type: "html" }] + } + } + }, + experiments: { + html: true, + css: true + } +}; diff --git a/test/configCases/html/parser-sources-list-only/index.js b/test/configCases/html/parser-sources-list-only/index.js new file mode 100644 index 00000000000..33529099f15 --- /dev/null +++ b/test/configCases/html/parser-sources-list-only/index.js @@ -0,0 +1,15 @@ +import page from "./page.html"; + +it("should treat an array without `'...'` as opt-out of defaults", () => { + // `./missing.css`, `./missing.js`, `./missing.png` don't exist; the + // build only succeeds because their attributes aren't in the user's + // sources list and are left untouched. + expect(page).toContain('href="./missing.css"'); + expect(page).toContain('src="./missing.js"'); + expect(page).toContain('src="./missing.png"'); +}); + +it("should still rewrite the user's custom data-src", () => { + expect(page).not.toContain('data-src="./lazy.png"'); + expect(page).toMatch(/data-src="[^"]+\.png"/); +}); diff --git a/test/configCases/html/parser-sources-list-only/lazy.png b/test/configCases/html/parser-sources-list-only/lazy.png new file mode 100644 index 00000000000..0dd1608e45a --- /dev/null +++ b/test/configCases/html/parser-sources-list-only/lazy.png @@ -0,0 +1,2 @@ +PNG + diff --git a/test/configCases/html/parser-sources-list-only/page.html b/test/configCases/html/parser-sources-list-only/page.html new file mode 100644 index 00000000000..6de6edd2c76 --- /dev/null +++ b/test/configCases/html/parser-sources-list-only/page.html @@ -0,0 +1,10 @@ + + + + + + + +image + + diff --git a/test/configCases/html/parser-sources-list-only/webpack.config.js b/test/configCases/html/parser-sources-list-only/webpack.config.js new file mode 100644 index 00000000000..72de73a49b2 --- /dev/null +++ b/test/configCases/html/parser-sources-list-only/webpack.config.js @@ -0,0 +1,22 @@ +"use strict"; + +/** @type {import("../../../../").Configuration} */ +module.exports = { + target: "web", + module: { + parser: { + html: { + // An array without `"..."` opts out of the default source list. + // Only `` is extracted as a webpack dependency; + // ``, ``, ` + + +" +`; diff --git a/test/configCases/html/preload-prefetch-shared/__snapshots__/ConfigTest.snap b/test/configCases/html/preload-prefetch-shared/__snapshots__/ConfigTest.snap new file mode 100644 index 00000000000..c59c37eb1a2 --- /dev/null +++ b/test/configCases/html/preload-prefetch-shared/__snapshots__/ConfigTest.snap @@ -0,0 +1,20 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigTestCases html preload-prefetch-shared exported tests should build a preload/prefetch target that is also used in code and as a real entry 1`] = ` +" + + +Test + + + + + + + +

Hello

+ + + +" +`; diff --git a/test/configCases/html/preload-prefetch-shared/counter.js b/test/configCases/html/preload-prefetch-shared/counter.js new file mode 100644 index 00000000000..d6f51329c3c --- /dev/null +++ b/test/configCases/html/preload-prefetch-shared/counter.js @@ -0,0 +1 @@ +export const state = { count: 0 }; diff --git a/test/configCases/html/preload-prefetch-shared/index.js b/test/configCases/html/preload-prefetch-shared/index.js new file mode 100644 index 00000000000..f39c9433584 --- /dev/null +++ b/test/configCases/html/preload-prefetch-shared/index.js @@ -0,0 +1,32 @@ +import { state } from "./counter.js"; +import "./used.js"; +import page from "./page.html"; + +it("should build a preload/prefetch target that is also used in code and as a real entry", () => { + expect(typeof page).toBe("string"); + expect(page).toMatchSnapshot(); + + // The real ` + + diff --git a/test/configCases/html/preload-prefetch-shared/shared.js b/test/configCases/html/preload-prefetch-shared/shared.js new file mode 100644 index 00000000000..34363e0a3e3 --- /dev/null +++ b/test/configCases/html/preload-prefetch-shared/shared.js @@ -0,0 +1 @@ +import "./used.js"; diff --git a/test/configCases/html/preload-prefetch-shared/test.config.js b/test/configCases/html/preload-prefetch-shared/test.config.js new file mode 100644 index 00000000000..2059a3f8977 --- /dev/null +++ b/test/configCases/html/preload-prefetch-shared/test.config.js @@ -0,0 +1,7 @@ +"use strict"; + +module.exports = { + findBundle() { + return ["./main.js"]; + } +}; diff --git a/test/configCases/html/preload-prefetch-shared/used.js b/test/configCases/html/preload-prefetch-shared/used.js new file mode 100644 index 00000000000..33332d84cc6 --- /dev/null +++ b/test/configCases/html/preload-prefetch-shared/used.js @@ -0,0 +1,3 @@ +import { state } from "./counter.js"; + +state.count++; diff --git a/test/configCases/html/preload-prefetch-shared/webpack.config.js b/test/configCases/html/preload-prefetch-shared/webpack.config.js new file mode 100644 index 00000000000..075c65f0f5f --- /dev/null +++ b/test/configCases/html/preload-prefetch-shared/webpack.config.js @@ -0,0 +1,18 @@ +"use strict"; + +/** @type {import("../../../../").Configuration} */ +module.exports = { + target: "web", + output: { + filename: "[name].js", + chunkFilename: "[name].chunk.js", + cssChunkFilename: "[name].chunk.css" + }, + optimization: { + chunkIds: "named" + }, + experiments: { + html: true, + css: true + } +}; diff --git a/test/configCases/html/preload-prefetch/__snapshots__/ConfigCacheTest.snap b/test/configCases/html/preload-prefetch/__snapshots__/ConfigCacheTest.snap new file mode 100644 index 00000000000..c99b7542738 --- /dev/null +++ b/test/configCases/html/preload-prefetch/__snapshots__/ConfigCacheTest.snap @@ -0,0 +1,19 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigCacheTestCases html preload-prefetch exported tests should rewrite of a bundled script/style to chunk URLs 1`] = ` +" + + +Test + + + + + + + +

Hello

+ + +" +`; diff --git a/test/configCases/html/preload-prefetch/__snapshots__/ConfigTest.snap b/test/configCases/html/preload-prefetch/__snapshots__/ConfigTest.snap new file mode 100644 index 00000000000..ed60341f161 --- /dev/null +++ b/test/configCases/html/preload-prefetch/__snapshots__/ConfigTest.snap @@ -0,0 +1,19 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigTestCases html preload-prefetch exported tests should rewrite of a bundled script/style to chunk URLs 1`] = ` +" + + +Test + + + + + + + +

Hello

+ + +" +`; diff --git a/test/configCases/html/preload-prefetch/index.js b/test/configCases/html/preload-prefetch/index.js new file mode 100644 index 00000000000..33496e3e9f7 --- /dev/null +++ b/test/configCases/html/preload-prefetch/index.js @@ -0,0 +1,28 @@ +import page from "./page.html"; + +it("should rewrite of a bundled script/style to chunk URLs", () => { + expect(typeof page).toBe("string"); + expect(page).toMatchSnapshot(); + + // `as="script"` preload/prefetch → JS chunk URL, tag otherwise kept. + expect(page).not.toContain('href="./preload-script.js"'); + expect(page).not.toContain('href="./prefetch-script.js"'); + expect(page).toMatch( + // + ); + expect(page).toMatch( + // + ); + + // `as="style"` preload → CSS chunk URL. + expect(page).not.toContain('href="./preload-style.css"'); + expect(page).toMatch( + // + ); + + // `webpackIgnore` leaves the resource hint untouched. + expect(page).toContain(''); + + // A resource hint references exactly one URL — no sibling tags are injected. + expect(page).not.toContain(" + + +Test + + + + + + + +

Hello

+ + diff --git a/test/configCases/html/preload-prefetch/prefetch-script.js b/test/configCases/html/preload-prefetch/prefetch-script.js new file mode 100644 index 00000000000..9f9123a5f45 --- /dev/null +++ b/test/configCases/html/preload-prefetch/prefetch-script.js @@ -0,0 +1 @@ +globalThis.PREFETCH_SCRIPT = "prefetch-script"; diff --git a/test/configCases/html/preload-prefetch/preload-script.js b/test/configCases/html/preload-prefetch/preload-script.js new file mode 100644 index 00000000000..ac48aa54b89 --- /dev/null +++ b/test/configCases/html/preload-prefetch/preload-script.js @@ -0,0 +1 @@ +globalThis.PRELOAD_SCRIPT = "preload-script"; diff --git a/test/configCases/html/preload-prefetch/preload-style.css b/test/configCases/html/preload-prefetch/preload-style.css new file mode 100644 index 00000000000..575d19f7b0e --- /dev/null +++ b/test/configCases/html/preload-prefetch/preload-style.css @@ -0,0 +1,3 @@ +body { + color: red; +} diff --git a/test/configCases/html/preload-prefetch/test.config.js b/test/configCases/html/preload-prefetch/test.config.js new file mode 100644 index 00000000000..2059a3f8977 --- /dev/null +++ b/test/configCases/html/preload-prefetch/test.config.js @@ -0,0 +1,7 @@ +"use strict"; + +module.exports = { + findBundle() { + return ["./main.js"]; + } +}; diff --git a/test/configCases/html/preload-prefetch/webpack.config.js b/test/configCases/html/preload-prefetch/webpack.config.js new file mode 100644 index 00000000000..075c65f0f5f --- /dev/null +++ b/test/configCases/html/preload-prefetch/webpack.config.js @@ -0,0 +1,18 @@ +"use strict"; + +/** @type {import("../../../../").Configuration} */ +module.exports = { + target: "web", + output: { + filename: "[name].js", + chunkFilename: "[name].chunk.js", + cssChunkFilename: "[name].chunk.css" + }, + optimization: { + chunkIds: "named" + }, + experiments: { + html: true, + css: true + } +}; diff --git a/test/configCases/html/script-src-classic/__snapshots__/ConfigCacheTest.snap b/test/configCases/html/script-src-classic/__snapshots__/ConfigCacheTest.snap new file mode 100644 index 00000000000..4390b8b67b9 --- /dev/null +++ b/test/configCases/html/script-src-classic/__snapshots__/ConfigCacheTest.snap @@ -0,0 +1,185 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigCacheTestCases html script-src-classic exported tests should emit IIFE-wrapped chunks for + + + + + + + + +" +`; diff --git a/test/configCases/html/script-src-classic/__snapshots__/ConfigTest.snap b/test/configCases/html/script-src-classic/__snapshots__/ConfigTest.snap new file mode 100644 index 00000000000..e67c162fe6b --- /dev/null +++ b/test/configCases/html/script-src-classic/__snapshots__/ConfigTest.snap @@ -0,0 +1,185 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigTestCases html script-src-classic exported tests should emit IIFE-wrapped chunks for + + + + + + + + +" +`; diff --git a/test/configCases/html/script-src-classic/classic-typed.js b/test/configCases/html/script-src-classic/classic-typed.js new file mode 100644 index 00000000000..8d9b9b48bd7 --- /dev/null +++ b/test/configCases/html/script-src-classic/classic-typed.js @@ -0,0 +1 @@ +module.exports = "classic typed entry"; diff --git a/test/configCases/html/script-src-classic/entry.js b/test/configCases/html/script-src-classic/entry.js new file mode 100644 index 00000000000..d8fffe7e01d --- /dev/null +++ b/test/configCases/html/script-src-classic/entry.js @@ -0,0 +1 @@ +module.exports = "first entry"; diff --git a/test/configCases/html/script-src-classic/index.js b/test/configCases/html/script-src-classic/index.js new file mode 100644 index 00000000000..f0a60d76764 --- /dev/null +++ b/test/configCases/html/script-src-classic/index.js @@ -0,0 +1,69 @@ +const fs = require("fs"); +const path = require("path"); + +const page = require("./page.html"); + +const readChunk = (name) => fs.readFileSync(path.resolve(__dirname, name), "utf-8"); + +// `String.prototype.matchAll` requires Node 12+; this test runs on Node +// 10 too, so collect all matches via a `regex.exec` loop instead. +const collectMatches = (str, regex) => { + const out = []; + let m; + while ((m = regex.exec(str)) !== null) out.push(m); + return out; +}; + +it("should rewrite script src attributes without changing the type attribute when output.module is off", () => { + expect(typeof page).toBe("string"); + expect(page).toMatchSnapshot(); + // Original source paths were rewritten. + expect(page).not.toContain('src="./entry.js"'); + expect(page).not.toContain('src="./other.js"'); + expect(page).not.toContain('src="./classic-typed.js"'); + expect(page).not.toContain('src="./module-entry.js"'); + // Classic + + + + + + + + diff --git a/test/configCases/html/script-src-classic/webpack.config.js b/test/configCases/html/script-src-classic/webpack.config.js new file mode 100644 index 00000000000..a60bb974151 --- /dev/null +++ b/test/configCases/html/script-src-classic/webpack.config.js @@ -0,0 +1,26 @@ +"use strict"; + +// No `experiments.outputModule` and no `output.module` — emitted chunks are +// classic IIFE-wrapped scripts. The parser must NOT auto-upgrade +// ` + + + + + + + +" +`; + +exports[`ConfigCacheTestCases html script-src-mixed exported tests should chain multiple + + + + + + + +" +`; + +exports[`ConfigTestCases html script-src-mixed exported tests should chain multiple + + + + + + + diff --git a/test/configCases/html/script-src-mixed/test.filter.js b/test/configCases/html/script-src-mixed/test.filter.js new file mode 100644 index 00000000000..f0f143b8e02 --- /dev/null +++ b/test/configCases/html/script-src-mixed/test.filter.js @@ -0,0 +1,9 @@ +"use strict"; + +// Tests use `import.meta.url` to locate emitted chunks at runtime and +// `globalThis` in fixtures, both of which require Node 12+. +module.exports = function filter() { + const major = Number(process.versions.node.split(".")[0]); + // the es2022 snapshot uses `Object.hasOwn`, available since Node 16.9 + return major >= 12 && typeof Object.hasOwn === "function"; +}; diff --git a/test/configCases/html/script-src-mixed/webpack.config.js b/test/configCases/html/script-src-mixed/webpack.config.js new file mode 100644 index 00000000000..73f5fad1ec0 --- /dev/null +++ b/test/configCases/html/script-src-mixed/webpack.config.js @@ -0,0 +1,30 @@ +"use strict"; + +/** @type {import("../../../../").Configuration} */ +module.exports = { + target: ["web", "es2022"], + node: { + __dirname: false, + __filename: false + }, + externalsPresets: { + node: true + }, + module: { + parser: { + javascript: { + importMeta: false + } + } + }, + output: { + chunkFilename: "[name].chunk.js" + }, + optimization: { + chunkIds: "named" + }, + experiments: { + html: true, + outputModule: true + } +}; diff --git a/test/configCases/html/script-src-sibling-attrs/entry.js b/test/configCases/html/script-src-sibling-attrs/entry.js new file mode 100644 index 00000000000..4fe51c72d64 --- /dev/null +++ b/test/configCases/html/script-src-sibling-attrs/entry.js @@ -0,0 +1 @@ +import "./style.css"; diff --git a/test/configCases/html/script-src-sibling-attrs/page.html b/test/configCases/html/script-src-sibling-attrs/page.html new file mode 100644 index 00000000000..699918b10e7 --- /dev/null +++ b/test/configCases/html/script-src-sibling-attrs/page.html @@ -0,0 +1,8 @@ + + + +script src sibling attrs + + +
Box
+ diff --git a/test/configCases/html/script-src-sibling-attrs/style.css b/test/configCases/html/script-src-sibling-attrs/style.css new file mode 100644 index 00000000000..49d13132599 --- /dev/null +++ b/test/configCases/html/script-src-sibling-attrs/style.css @@ -0,0 +1 @@ +.hero { color: green; } diff --git a/test/configCases/html/script-src-sibling-attrs/test.config.js b/test/configCases/html/script-src-sibling-attrs/test.config.js new file mode 100644 index 00000000000..1f8d2c48be8 --- /dev/null +++ b/test/configCases/html/script-src-sibling-attrs/test.config.js @@ -0,0 +1,7 @@ +"use strict"; + +module.exports = { + findBundle() { + return ["./test.js"]; + } +}; diff --git a/test/configCases/html/script-src-sibling-attrs/test.js b/test/configCases/html/script-src-sibling-attrs/test.js new file mode 100644 index 00000000000..c159f46307c --- /dev/null +++ b/test/configCases/html/script-src-sibling-attrs/test.js @@ -0,0 +1,29 @@ +const fs = require("fs"); +const path = require("path"); + +const readFile = (name) => + fs.readFileSync(path.resolve(__dirname, name), "utf-8"); + +it("should copy quoted, bare and unquoted CSP/fetch attributes byte-exact onto the synthesized sibling ", () => { + const extracted = readFile("page.html"); + + const linkMatch = extracted.match(/]*>/); + expect(linkMatch).not.toBeNull(); + const linkTag = linkMatch[0]; + expect(linkTag).toMatch(/href="[^"]+\.css"/); + + // Each copyable attribute is carried back in its original source form — + // quoted, bare (valueless), and unquoted — in the fixed nonce/crossorigin/ + // referrerpolicy order, regardless of source order. + expect(linkTag).toContain( + ' nonce="tok-1" crossorigin referrerpolicy=origin' + ); + // `defer` is not a copyable attribute, so it must not leak onto the link. + expect(linkTag).not.toContain("defer"); + + // The link precedes the script so the stylesheet download starts first. + const scriptIdx = extracted.indexOf("` HTML entry whose JS imports CSS, carrying the three +// copyable CSP/fetch attributes in all three source forms — quoted +// (`nonce="…"`), bare (`crossorigin`), and unquoted (`referrerpolicy=…`). +// The synthesized sibling `` must carry each one back byte-exact, +// exercising every branch of the parser's `attrSourceSpan`. + +const fs = require("fs"); +const path = require("path"); +const webpack = require("../../../../"); + +/** @type {import("../../../../").Configuration} */ +module.exports = { + target: "web", + entry: { + page: "./page.html" + }, + output: { + filename: "[name].js", + chunkFilename: "[name].chunk.js" + }, + optimization: { chunkIds: "named" }, + experiments: { html: true, css: true }, + plugins: [ + { + apply(compiler) { + compiler.hooks.compilation.tap("Test", (compilation) => { + compilation.hooks.processAssets.tap( + { + name: "copy-test", + stage: + compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL + }, + () => { + const data = fs.readFileSync(path.resolve(__dirname, "test.js")); + compilation.emitAsset( + "test.js", + new webpack.sources.RawSource(data) + ); + } + ); + }); + } + } + ] +}; diff --git a/test/configCases/html/script-src/__snapshots__/ConfigCacheTest.snap b/test/configCases/html/script-src/__snapshots__/ConfigCacheTest.snap new file mode 100644 index 00000000000..df874b97b34 --- /dev/null +++ b/test/configCases/html/script-src/__snapshots__/ConfigCacheTest.snap @@ -0,0 +1,152 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigCacheTestCases html script-src exported tests should bundle + + +

Hello

+ + + + + + + + + + +" +`; diff --git a/test/configCases/html/script-src/__snapshots__/ConfigTest.snap b/test/configCases/html/script-src/__snapshots__/ConfigTest.snap new file mode 100644 index 00000000000..2d9c036c086 --- /dev/null +++ b/test/configCases/html/script-src/__snapshots__/ConfigTest.snap @@ -0,0 +1,152 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigTestCases html script-src exported tests should bundle + + +

Hello

+ + + + + + + + + + +" +`; diff --git a/test/configCases/html/script-src/classic-typed.js b/test/configCases/html/script-src/classic-typed.js new file mode 100644 index 00000000000..8d9b9b48bd7 --- /dev/null +++ b/test/configCases/html/script-src/classic-typed.js @@ -0,0 +1 @@ +module.exports = "classic typed entry"; diff --git a/test/configCases/html/script-src/entry.js b/test/configCases/html/script-src/entry.js new file mode 100644 index 00000000000..d8fffe7e01d --- /dev/null +++ b/test/configCases/html/script-src/entry.js @@ -0,0 +1 @@ +module.exports = "first entry"; diff --git a/test/configCases/html/script-src/icon.png b/test/configCases/html/script-src/icon.png new file mode 100644 index 00000000000..e69de29bb2d diff --git a/test/configCases/html/script-src/importmap.json b/test/configCases/html/script-src/importmap.json new file mode 100644 index 00000000000..f6ca8454c56 --- /dev/null +++ b/test/configCases/html/script-src/importmap.json @@ -0,0 +1,3 @@ +{ + "imports": {} +} diff --git a/test/configCases/html/script-src/index.js b/test/configCases/html/script-src/index.js new file mode 100644 index 00000000000..e2636061144 --- /dev/null +++ b/test/configCases/html/script-src/index.js @@ -0,0 +1,96 @@ +import fs from "fs"; +import path from "path"; +import { fileURLToPath } from "url"; + +import page from "./page.html"; + +const here = path.dirname(fileURLToPath(import.meta.url)); + +const readChunk = (name) => fs.readFileSync(path.resolve(here, name), "utf-8"); + +// Document-order list of every script-src chunk url emitted into the page. +// Classic + + +

Hello

+ + + + + + + + + + diff --git a/test/configCases/html/script-src/style.css b/test/configCases/html/script-src/style.css new file mode 100644 index 00000000000..991912d894b --- /dev/null +++ b/test/configCases/html/script-src/style.css @@ -0,0 +1 @@ +body { color: red; } diff --git a/test/configCases/html/script-src/test.filter.js b/test/configCases/html/script-src/test.filter.js new file mode 100644 index 00000000000..57fde62104e --- /dev/null +++ b/test/configCases/html/script-src/test.filter.js @@ -0,0 +1,10 @@ +"use strict"; + +// Tests use `import.meta.url` to locate emitted chunks at runtime, which +// requires the bundle to run as a real ES module. Jest's ESM support +// (via `--experimental-vm-modules`) needs Node 12+. +module.exports = function filter() { + const major = Number(process.versions.node.split(".")[0]); + // the es2022 snapshot uses `Object.hasOwn`, available since Node 16.9 + return major >= 12 && typeof Object.hasOwn === "function"; +}; diff --git a/test/configCases/html/script-src/webpack.config.js b/test/configCases/html/script-src/webpack.config.js new file mode 100644 index 00000000000..73f5fad1ec0 --- /dev/null +++ b/test/configCases/html/script-src/webpack.config.js @@ -0,0 +1,30 @@ +"use strict"; + +/** @type {import("../../../../").Configuration} */ +module.exports = { + target: ["web", "es2022"], + node: { + __dirname: false, + __filename: false + }, + externalsPresets: { + node: true + }, + module: { + parser: { + javascript: { + importMeta: false + } + } + }, + output: { + chunkFilename: "[name].chunk.js" + }, + optimization: { + chunkIds: "named" + }, + experiments: { + html: true, + outputModule: true + } +}; diff --git a/test/configCases/html/script-type-unquoted/__snapshots__/ConfigCacheTest.snap b/test/configCases/html/script-type-unquoted/__snapshots__/ConfigCacheTest.snap new file mode 100644 index 00000000000..0f99b4c61cf --- /dev/null +++ b/test/configCases/html/script-type-unquoted/__snapshots__/ConfigCacheTest.snap @@ -0,0 +1,12 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigCacheTestCases html script-type-unquoted exported tests should strip unquoted type=module attr when output.module is false 1`] = ` +" + +script-type-unquoted + + + + +" +`; diff --git a/test/configCases/html/script-type-unquoted/__snapshots__/ConfigTest.snap b/test/configCases/html/script-type-unquoted/__snapshots__/ConfigTest.snap new file mode 100644 index 00000000000..3e007a5e4fc --- /dev/null +++ b/test/configCases/html/script-type-unquoted/__snapshots__/ConfigTest.snap @@ -0,0 +1,12 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigTestCases html script-type-unquoted exported tests should strip unquoted type=module attr when output.module is false 1`] = ` +" + +script-type-unquoted + + + + +" +`; diff --git a/test/configCases/html/script-type-unquoted/entry.js b/test/configCases/html/script-type-unquoted/entry.js new file mode 100644 index 00000000000..efeee5db16c --- /dev/null +++ b/test/configCases/html/script-type-unquoted/entry.js @@ -0,0 +1 @@ +export const value = 1; diff --git a/test/configCases/html/script-type-unquoted/index.js b/test/configCases/html/script-type-unquoted/index.js new file mode 100644 index 00000000000..d403e2ab246 --- /dev/null +++ b/test/configCases/html/script-type-unquoted/index.js @@ -0,0 +1,8 @@ +import page from "./page.html"; + +it("should strip unquoted type=module attr when output.module is false", () => { + expect(page).not.toContain("type=module"); + expect(page).not.toContain('type="module"'); + expect(page).toMatch(/ + + diff --git a/test/configCases/html/script-type-unquoted/webpack.config.js b/test/configCases/html/script-type-unquoted/webpack.config.js new file mode 100644 index 00000000000..d9ea1482649 --- /dev/null +++ b/test/configCases/html/script-type-unquoted/webpack.config.js @@ -0,0 +1,8 @@ +"use strict"; + +/** @type {import("../../../../").Configuration} */ +module.exports = { + target: "web", + output: { module: false }, + experiments: { html: true, outputModule: true } +}; diff --git a/test/configCases/html/skip-behavior/__snapshots__/ConfigCacheTest.snap b/test/configCases/html/skip-behavior/__snapshots__/ConfigCacheTest.snap new file mode 100644 index 00000000000..f250b594bdb --- /dev/null +++ b/test/configCases/html/skip-behavior/__snapshots__/ConfigCacheTest.snap @@ -0,0 +1,19 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigCacheTestCases html skip-behavior exported tests should silently skip whitespace-only source attribute values 1`] = ` +" + +skip behavior + + + +\\"ws-only\\" +\\"ws-only-srcset\\" + + +\\"ok\\" + + + +" +`; diff --git a/test/configCases/html/skip-behavior/__snapshots__/ConfigTest.snap b/test/configCases/html/skip-behavior/__snapshots__/ConfigTest.snap new file mode 100644 index 00000000000..e7c3c98a724 --- /dev/null +++ b/test/configCases/html/skip-behavior/__snapshots__/ConfigTest.snap @@ -0,0 +1,19 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigTestCases html skip-behavior exported tests should silently skip whitespace-only source attribute values 1`] = ` +" + +skip behavior + + + +\\"ws-only\\" +\\"ws-only-srcset\\" + + +\\"ok\\" + + + +" +`; diff --git a/test/configCases/html/skip-behavior/image.png b/test/configCases/html/skip-behavior/image.png new file mode 100644 index 00000000000..b74b839e2b8 Binary files /dev/null and b/test/configCases/html/skip-behavior/image.png differ diff --git a/test/configCases/html/skip-behavior/index.js b/test/configCases/html/skip-behavior/index.js new file mode 100644 index 00000000000..a4f228e0dd1 --- /dev/null +++ b/test/configCases/html/skip-behavior/index.js @@ -0,0 +1,5 @@ +import page from "./page.html"; + +it("should silently skip whitespace-only source attribute values", () => { + expect(page).toMatchSnapshot(); +}); diff --git a/test/configCases/html/skip-behavior/page.html b/test/configCases/html/skip-behavior/page.html new file mode 100644 index 00000000000..f30b6e09417 --- /dev/null +++ b/test/configCases/html/skip-behavior/page.html @@ -0,0 +1,14 @@ + + +skip behavior + + + +ws-only +ws-only-srcset + + +ok + + + diff --git a/test/configCases/html/skip-behavior/webpack.config.js b/test/configCases/html/skip-behavior/webpack.config.js new file mode 100644 index 00000000000..2d275504b64 --- /dev/null +++ b/test/configCases/html/skip-behavior/webpack.config.js @@ -0,0 +1,10 @@ +"use strict"; + +/** @type {import("../../../../").Configuration} */ +module.exports = { + devtool: false, + target: "web", + experiments: { + html: true + } +}; diff --git a/test/configCases/html/sources/__snapshots__/ConfigCacheTest.snap b/test/configCases/html/sources/__snapshots__/ConfigCacheTest.snap new file mode 100644 index 00000000000..f715074da89 --- /dev/null +++ b/test/configCases/html/sources/__snapshots__/ConfigCacheTest.snap @@ -0,0 +1,240 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigCacheTestCases html sources exported tests should handle source 1`] = ` +" + +Test + + +\\"img\\" +\\"img\\" +\\"img\\" + + + + + +\\"Elva +\\"Elva +\\"Elva +\\"Elva +\\"Elva +\\"Elva +\\"Elva +\\"Elva +\\"Elva + +
+ +
+ + + + + +
text
+
text
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Text + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +\\"Red + + +vincetanan@gmail.com +vince@gmail.com + +

Text

+

Text

+

Text

+ +\\"Smiley +\\"Smiley +\\"Elva +\\"Elva + +\\"multi + +\\"Red + +\\"\\" + +\\"test\\" +\\"test\\" +\\"test\\" + +\\"test\\" + + + +" +`; diff --git a/test/configCases/html/sources/__snapshots__/ConfigTest.snap b/test/configCases/html/sources/__snapshots__/ConfigTest.snap new file mode 100644 index 00000000000..518f92e41e4 --- /dev/null +++ b/test/configCases/html/sources/__snapshots__/ConfigTest.snap @@ -0,0 +1,240 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigTestCases html sources exported tests should handle source 1`] = ` +" + +Test + + +\\"img\\" +\\"img\\" +\\"img\\" + + + + + +\\"Elva +\\"Elva +\\"Elva +\\"Elva +\\"Elva +\\"Elva +\\"Elva +\\"Elva +\\"Elva + +
+ +
+ + + + + +
text
+
text
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Text + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +\\"Red + + +vincetanan@gmail.com +vince@gmail.com + +

Text

+

Text

+

Text

+ +\\"Smiley +\\"Smiley +\\"Elva +\\"Elva + +\\"multi + +\\"Red + +\\"\\" + +\\"test\\" +\\"test\\" +\\"test\\" + +\\"test\\" + + + +" +`; diff --git a/test/configCases/html/sources/browserconfig.xml b/test/configCases/html/sources/browserconfig.xml new file mode 100644 index 00000000000..2a8c8169f0b --- /dev/null +++ b/test/configCases/html/sources/browserconfig.xml @@ -0,0 +1,19 @@ + + + + + + + + + #000000 + + + + + + 30 + 1 + + + diff --git a/test/configCases/html/sources/example.ogg b/test/configCases/html/sources/example.ogg new file mode 100644 index 00000000000..a39a4e5a6a2 Binary files /dev/null and b/test/configCases/html/sources/example.ogg differ diff --git a/test/configCases/html/sources/example.vtt b/test/configCases/html/sources/example.vtt new file mode 100644 index 00000000000..e69de29bb2d diff --git a/test/configCases/html/sources/favicon.ico b/test/configCases/html/sources/favicon.ico new file mode 100644 index 00000000000..b74b839e2b8 Binary files /dev/null and b/test/configCases/html/sources/favicon.ico differ diff --git a/test/configCases/html/sources/file.pdf b/test/configCases/html/sources/file.pdf new file mode 100644 index 00000000000..e69de29bb2d diff --git a/test/configCases/html/sources/icons.svg b/test/configCases/html/sources/icons.svg new file mode 100644 index 00000000000..bd21f5337b8 --- /dev/null +++ b/test/configCases/html/sources/icons.svg @@ -0,0 +1 @@ + diff --git a/test/configCases/html/sources/image.png b/test/configCases/html/sources/image.png new file mode 100644 index 00000000000..b74b839e2b8 Binary files /dev/null and b/test/configCases/html/sources/image.png differ diff --git a/test/configCases/html/sources/index.js b/test/configCases/html/sources/index.js new file mode 100644 index 00000000000..ec228bc4f98 --- /dev/null +++ b/test/configCases/html/sources/index.js @@ -0,0 +1,5 @@ +import page from "./page.html"; + +it("should handle source", () => { + expect(page).toMatchSnapshot(); +}); diff --git a/test/configCases/html/sources/music.mp3 b/test/configCases/html/sources/music.mp3 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/test/configCases/html/sources/page.html b/test/configCases/html/sources/page.html new file mode 100644 index 00000000000..80fc3f36147 --- /dev/null +++ b/test/configCases/html/sources/page.html @@ -0,0 +1,242 @@ + + +Test + + +img +img +img + + + + + +Elva dressed as a fairy +Elva dressed as a fairy +Elva dressed as a fairy +Elva dressed as a fairy +Elva dressed as a fairy +Elva dressed as a fairy +Elva dressed as a fairy +Elva dressed as a fairy +Elva dressed as a fairy + +
+ +
+ + + + + +
text
+
text
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Text + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Red dot + + +vincetanan@gmail.com +vince@gmail.com + +

Text

+

Text

+

Text

+ +Smiley face +Smiley face +Elva dressed as a fairy +Elva dressed as a fairy + +multi
+line
+alt + +Red dot + + + +test +test +test + +test + + + diff --git a/test/configCases/html/sources/pixel.png b/test/configCases/html/sources/pixel.png new file mode 100644 index 00000000000..0f2de3749df Binary files /dev/null and b/test/configCases/html/sources/pixel.png differ diff --git a/test/configCases/html/sources/site.webmanifest b/test/configCases/html/sources/site.webmanifest new file mode 100644 index 00000000000..9e26dfeeb6e --- /dev/null +++ b/test/configCases/html/sources/site.webmanifest @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/test/configCases/html/sources/sound.mp3 b/test/configCases/html/sources/sound.mp3 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/test/configCases/html/sources/video.mp4 b/test/configCases/html/sources/video.mp4 new file mode 100644 index 00000000000..e69de29bb2d diff --git a/test/configCases/html/sources/webpack.config.js b/test/configCases/html/sources/webpack.config.js new file mode 100644 index 00000000000..f35cdef83fa --- /dev/null +++ b/test/configCases/html/sources/webpack.config.js @@ -0,0 +1,13 @@ +"use strict"; + +/** @type {import("../../../../").Configuration} */ +module.exports = { + devtool: false, + target: "web", + optimization: { + concatenateModules: true + }, + experiments: { + html: true + } +}; diff --git a/test/configCases/html/sources/webpack.svg b/test/configCases/html/sources/webpack.svg new file mode 100644 index 00000000000..07e7602cc85 --- /dev/null +++ b/test/configCases/html/sources/webpack.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git "a/test/configCases/html/sources/\360\237\230\200abc.png" "b/test/configCases/html/sources/\360\237\230\200abc.png" new file mode 100644 index 00000000000..b74b839e2b8 Binary files /dev/null and "b/test/configCases/html/sources/\360\237\230\200abc.png" differ diff --git a/test/configCases/html/srcset/errors.js b/test/configCases/html/srcset/errors.js new file mode 100644 index 00000000000..759d6efa493 --- /dev/null +++ b/test/configCases/html/srcset/errors.js @@ -0,0 +1,217 @@ +"use strict"; + +// Generated against the parser behavior: character references in attribute +// values are decoded before srcset parsing (entity-encoded ASCII whitespace +// separates candidates, like in browsers), so parse-error messages and +// unresolvable URLs below contain the decoded characters, while rewrite +// spans still map back to the raw source text. +module.exports = [ + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'image\.png 480w 2x broken' at 'broken'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'image\.png broken' at 'broken'/, + /Bad value for attribute "srcset" on element "img": Must contain one or more image candidate strings/, + /Bad value for attribute "srcset" on element "img": Must contain one or more image candidate strings/, + /Bad value for attribute "srcset" on element "img": Must contain one or more image candidate strings/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'image\.png 0h, image\.png 800w' at '0h'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in ',a foo' at 'foo'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'a \(,' at '\(,'/, + /Bad value for attribute "srcset" on element "img": Must contain one or more image candidate strings/, + /Bad value for attribute "srcset" on element "img": Must contain one or more image candidate strings/, + /Module not found: Error: Can't resolve '\u000B\u000Bdata:,a\u000B\u000B1x\u000B\u000B'/, + /Module not found: Error: Can't resolve '\u000E\u000Edata:,a\u000E\u000E1x\u000E\u000E'/, + /Module not found: Error: Can't resolve '\u000F\u000Fdata:,a\u000F\u000F1x\u000F\u000F'/, + /Module not found: Error: Can't resolve '\u0010\u0010data:,a\u0010\u00101x\u0010\u0010'/, + /Module not found: Error: Can't resolve '\u00A0data:,a'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \( , data:,b 1x, \), data:,c' at '\( , data:,b 1x, \)'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \(\(\( , data:,b 1x, \), data:,c' at '\(\(\( , data:,b 1x, \)'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \[ , data:,b 1x, \], data:,c' at '\['/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \{ , data:,b 1x, \}, data:,c' at '\{'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a, data:,b \(' at '\('/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a, data:,b \( {2}' at '\( {2}'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a, data:,b \(,' at '\(,'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a, data:,b \(x' at '\(x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a, data:,b \(\)' at '\(\)'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \(, data:,b' at '\(, data:,b'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \/\*, data:,b, data:,c \*\/' at '\/\*'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \/\/, data:,b' at '\/\/'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a foo' at 'foo'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a foo foo' at 'foo'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a foo 1x' at '1x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a foo 1x foo' at 'foo'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a foo 1w' at '1w'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a foo 1w foo' at 'foo'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1x 1x' at '1x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w 1w' at '1w'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1h 1h' at '1h'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w 1x' at '1x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1x 1w' at '1w'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1h 1x' at '1x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1h 1w 1x' at '1x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1x 1w 1h' at '1h'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1h foo' at 'foo'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a foo 1h' at '1h'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 0w' at '0w'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a -1w' at '-1w'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w -1w' at '-1w'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1\.0w' at '1\.0w'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w 1\.0w' at '1\.0w'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1e0w' at '1e0w'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w 1e0w' at '1e0w'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1www' at '1www'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w 1www' at '1www'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w \+1w' at '\+1w'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1W' at '1W'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w 1W' at '1W'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a Infinityw' at 'Infinityw'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w Infinityw' at 'Infinityw'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a NaNw' at 'NaNw'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w NaNw' at 'NaNw'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 0x1w' at '0x1w'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1\u0001w' at '1\u0001w'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1\u00A0w' at '1\u00A0w'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1\u1680w' at '1\u1680w'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1\u2000w' at '1\u2000w'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1\u2001w' at '1\u2001w'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1\u2002w' at '1\u2002w'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1\u2003w' at '1\u2003w'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1\u2004w' at '1\u2004w'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1\u2005w' at '1\u2005w'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1\u2006w' at '1\u2006w'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1\u2007w' at '1\u2007w'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1\u2008w' at '1\u2008w'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1\u2009w' at '1\u2009w'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1\u200Aw' at '1\u200Aw'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1\u200Cw' at '1\u200Cw'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1\u200Dw' at '1\u200Dw'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1\u202Fw' at '1\u202Fw'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1\u205Fw' at '1\u205Fw'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1\u3000w' at '1\u3000w'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1\uFEFFw' at '1\uFEFFw'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \u00011w' at '\u00011w'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \u00A01w' at '\u00A01w'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \u16801w' at '\u16801w'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \u20001w' at '\u20001w'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \u20011w' at '\u20011w'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \u20021w' at '\u20021w'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \u20031w' at '\u20031w'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \u20041w' at '\u20041w'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \u20051w' at '\u20051w'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \u20061w' at '\u20061w'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \u20071w' at '\u20071w'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \u20081w' at '\u20081w'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \u20091w' at '\u20091w'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \u200A1w' at '\u200A1w'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \u200C1w' at '\u200C1w'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \u200D1w' at '\u200D1w'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \u202F1w' at '\u202F1w'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \u205F1w' at '\u205F1w'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \u30001w' at '\u30001w'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \uFEFF1w' at '\uFEFF1w'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1x -0x' at '-0x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a -1x' at '-1x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1x -1x' at '-1x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a -x' at '-x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \.x' at '\.x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a -\.x' at '-\.x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1\.x' at '1\.x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1x 1\.5e1x' at '1\.5e1x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1x 1e1\.5x' at '1e1\.5x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1x 1\.0x' at '1\.0x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \+1x' at '\+1x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1X' at '1X'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a Infinityx' at 'Infinityx'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a NaNx' at 'NaNx'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 0x1x' at '0x1x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 0X1x' at '0X1x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1\u0001x' at '1\u0001x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1\u00A0x' at '1\u00A0x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1\u1680x' at '1\u1680x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1\u2000x' at '1\u2000x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1\u2001x' at '1\u2001x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1\u2002x' at '1\u2002x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1\u2003x' at '1\u2003x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1\u2004x' at '1\u2004x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1\u2005x' at '1\u2005x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1\u2006x' at '1\u2006x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1\u2007x' at '1\u2007x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1\u2008x' at '1\u2008x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1\u2009x' at '1\u2009x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1\u200Ax' at '1\u200Ax'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1\u200Cx' at '1\u200Cx'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1\u200Dx' at '1\u200Dx'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1\u202Fx' at '1\u202Fx'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1\u205Fx' at '1\u205Fx'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1\u3000x' at '1\u3000x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1\uFEFFx' at '1\uFEFFx'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \u00011x' at '\u00011x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \u00A01x' at '\u00A01x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \u16801x' at '\u16801x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \u20001x' at '\u20001x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \u20011x' at '\u20011x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \u20021x' at '\u20021x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \u20031x' at '\u20031x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \u20041x' at '\u20041x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \u20051x' at '\u20051x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \u20061x' at '\u20061x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \u20071x' at '\u20071x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \u20081x' at '\u20081x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \u20091x' at '\u20091x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \u200A1x' at '\u200A1x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \u200C1x' at '\u200C1x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \u200D1x' at '\u200D1x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \u202F1x' at '\u202F1x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \u205F1x' at '\u205F1x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \u30001x' at '\u30001x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a \uFEFF1x' at '\uFEFF1x'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w 0h' at '0h'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w -1h' at '-1h'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w 1\.0h' at '1\.0h'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w 1e0h' at '1e0h'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w 1hhh' at '1hhh'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w 1H' at '1H'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w Infinityh' at 'Infinityh'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w NaNh' at 'NaNh'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 0x1h' at '0x1h'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 0X1h' at '0X1h'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w 1\u0001h' at '1\u0001h'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w 1\u00A0h' at '1\u00A0h'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w 1\u1680h' at '1\u1680h'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w 1\u2000h' at '1\u2000h'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w 1\u2001h' at '1\u2001h'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w 1\u2002h' at '1\u2002h'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w 1\u2003h' at '1\u2003h'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w 1\u2004h' at '1\u2004h'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w 1\u2005h' at '1\u2005h'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w 1\u2006h' at '1\u2006h'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w 1\u2007h' at '1\u2007h'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w 1\u2008h' at '1\u2008h'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w 1\u2009h' at '1\u2009h'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w 1\u200Ah' at '1\u200Ah'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w 1\u200Ch' at '1\u200Ch'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w 1\u200Dh' at '1\u200Dh'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w 1\u202Fh' at '1\u202Fh'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w 1\u205Fh' at '1\u205Fh'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w 1\u3000h' at '1\u3000h'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w 1\uFEFFh' at '1\uFEFFh'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w \u00011h' at '\u00011h'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w \u00A01h' at '\u00A01h'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w \u16801h' at '\u16801h'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w \u20001h' at '\u20001h'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w \u20011h' at '\u20011h'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w \u20021h' at '\u20021h'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w \u20031h' at '\u20031h'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w \u20041h' at '\u20041h'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w \u20051h' at '\u20051h'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w \u20061h' at '\u20061h'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w \u20071h' at '\u20071h'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w \u20081h' at '\u20081h'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w \u20091h' at '\u20091h'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w \u200A1h' at '\u200A1h'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w \u200C1h' at '\u200C1h'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w \u200D1h' at '\u200D1h'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w \u202F1h' at '\u202F1h'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w \u205F1h' at '\u205F1h'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w \u30001h' at '\u30001h'/, + /Bad value for attribute "srcset" on element "img": Invalid srcset descriptor found in 'data:,a 1w \uFEFF1h' at '\uFEFF1h'/, + /Bad value for attribute "srcset" on element "img": Must contain one or more image candidate strings/, + /Bad value for attribute "srcset" on element "img": Must contain one or more image candidate strings/ +]; diff --git a/test/configCases/html/srcset/image.png b/test/configCases/html/srcset/image.png new file mode 100644 index 00000000000..e69de29bb2d diff --git a/test/configCases/html/srcset/index.js b/test/configCases/html/srcset/index.js new file mode 100644 index 00000000000..82f61fb7cb3 --- /dev/null +++ b/test/configCases/html/srcset/index.js @@ -0,0 +1,5 @@ +import page from "./page.html"; + +it("should work and handle different syntax", () => { + expect(page).toMatchSnapshot(); +}); diff --git a/test/configCases/html/srcset/page.html b/test/configCases/html/srcset/page.html new file mode 100644 index 00000000000..a646d3c8b19 --- /dev/null +++ b/test/configCases/html/srcset/page.html @@ -0,0 +1,293 @@ +Elva dressed as a fairy +Elva dressed as a fairy + + + + +Elva dressed as a fairy +Elva dressed as a fairy +Elva dressed as a fairy + +Elva dressed as a fairy + +Elva dressed as a fairy + + + +Elva dressed as a fairy +Elva dressed as a fairy + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Elva dressed as a fairy +Elva dressed as a fairy +Elva dressed as a fairy +Elva dressed as a fairy +Elva dressed as a fairy +Elva dressed as a fairy +Elva dressed as a fairy +Elva dressed as a fairy +Elva dressed as a fairy +Elva dressed as a fairy +Elva dressed as a fairy + + + + + + + + + + + +vincetanan@gmail.com +vince@gmail.com diff --git a/test/configCases/html/srcset/test.filter.js b/test/configCases/html/srcset/test.filter.js new file mode 100644 index 00000000000..96ddb2717f2 --- /dev/null +++ b/test/configCases/html/srcset/test.filter.js @@ -0,0 +1,3 @@ +"use strict"; + +module.exports = (config) => !config.cache; diff --git a/test/configCases/html/srcset/webpack.config.js b/test/configCases/html/srcset/webpack.config.js new file mode 100644 index 00000000000..bc4cbc8fe56 --- /dev/null +++ b/test/configCases/html/srcset/webpack.config.js @@ -0,0 +1,9 @@ +"use strict"; + +/** @type {import("../../../../").Configuration} */ +module.exports = { + target: "web", + experiments: { + html: true + } +}; diff --git a/test/configCases/html/style-attribute/__snapshots__/ConfigCacheTest.snap b/test/configCases/html/style-attribute/__snapshots__/ConfigCacheTest.snap new file mode 100644 index 00000000000..8112f3f95d6 --- /dev/null +++ b/test/configCases/html/style-attribute/__snapshots__/ConfigCacheTest.snap @@ -0,0 +1,21 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigCacheTestCases html style-attribute exported tests should resolve url() references inside \`style\` attributes 1`] = ` +" + + +Style Attribute Test + + +
Single url
+

No url here — left untouched

+image-set +
Multiple urls
+
Entity in url
+
Entity round-trip
+
Entity-obfuscated url()
+
Empty style — left untouched
+ + +" +`; diff --git a/test/configCases/html/style-attribute/__snapshots__/ConfigTest.snap b/test/configCases/html/style-attribute/__snapshots__/ConfigTest.snap new file mode 100644 index 00000000000..e7a99933edd --- /dev/null +++ b/test/configCases/html/style-attribute/__snapshots__/ConfigTest.snap @@ -0,0 +1,21 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigTestCases html style-attribute exported tests should resolve url() references inside \`style\` attributes 1`] = ` +" + + +Style Attribute Test + + +
Single url
+

No url here — left untouched

+image-set +
Multiple urls
+
Entity in url
+
Entity round-trip
+
Entity-obfuscated url()
+
Empty style — left untouched
+ + +" +`; diff --git a/test/configCases/html/style-attribute/index.js b/test/configCases/html/style-attribute/index.js new file mode 100644 index 00000000000..ac026c13109 --- /dev/null +++ b/test/configCases/html/style-attribute/index.js @@ -0,0 +1,35 @@ +import page from "./page.html"; + +it("should resolve url() references inside `style` attributes", () => { + expect(typeof page).toBe("string"); + expect(page).toMatchSnapshot(); + + // The CSS declarations themselves pass through unchanged. + expect(page).toContain("color: red"); + + // `url(./pixel.png)` (quoted and unquoted) is rewritten to an asset URL. + expect(page).not.toContain("url(./pixel.png)"); + expect(page).not.toContain("url('./pixel.png')"); + expect(page).toMatch(/url\(handled-pixel\.png\)/); + + // `image-set()` references are resolved too (the string form is + // rewritten to a `url()`). + expect(page).toMatch(/image-set\(url\(handled-pixel\.png\) 1x\)/); + + // A `style` attribute with no URL-bearing function is left untouched. + expect(page).toContain('style="color: blue;"'); +}); + +it("should decode character references and re-escape on write-back", () => { + // `url(./pixel.png)` decodes to `./pixel.png` and resolves. + expect(page).not.toContain("pixel.png"); + + // A decoded `"` (from `"`) is re-escaped when the processed CSS is + // written back into the attribute (quotes are escaped for any quoting + // context), keeping the HTML valid. + expect(page).toMatch(/style="--quote: '"';[^"]*"/); + + // The url() pre-filter runs on the decoded value, so `url(` is + // recognized and resolved. + expect(page).not.toContain("url(./pixel.png)"); +}); diff --git a/test/configCases/html/style-attribute/page.html b/test/configCases/html/style-attribute/page.html new file mode 100644 index 00000000000..ae478ed0d44 --- /dev/null +++ b/test/configCases/html/style-attribute/page.html @@ -0,0 +1,16 @@ + + + +Style Attribute Test + + +
Single url
+

No url here — left untouched

+image-set +
Multiple urls
+
Entity in url
+
Entity round-trip
+
Entity-obfuscated url()
+
Empty style — left untouched
+ + diff --git a/test/configCases/html/style-attribute/pixel.png b/test/configCases/html/style-attribute/pixel.png new file mode 100644 index 00000000000..e69de29bb2d diff --git a/test/configCases/html/style-attribute/webpack.config.js b/test/configCases/html/style-attribute/webpack.config.js new file mode 100644 index 00000000000..fe3ab4ea595 --- /dev/null +++ b/test/configCases/html/style-attribute/webpack.config.js @@ -0,0 +1,15 @@ +"use strict"; + +/** @type {import("../../../../").Configuration} */ +module.exports = { + devtool: false, + target: "web", + output: { + pathinfo: false, + assetModuleFilename: "handled-[name][ext]" + }, + experiments: { + html: true, + css: true + } +}; diff --git a/test/configCases/html/style-tag-context/index.js b/test/configCases/html/style-tag-context/index.js new file mode 100644 index 00000000000..64cfa6dfb9c --- /dev/null +++ b/test/configCases/html/style-tag-context/index.js @@ -0,0 +1,12 @@ +// The HTML file lives in `sub/`, while this entry (which sets the +// compilation context for webpack) lives one level up. Relative `url(...)` +// references inside an inline ` + + + diff --git a/test/configCases/html/style-tag-context/sub/pixel.png b/test/configCases/html/style-tag-context/sub/pixel.png new file mode 100644 index 00000000000..e69de29bb2d diff --git a/test/configCases/html/style-tag-context/webpack.config.js b/test/configCases/html/style-tag-context/webpack.config.js new file mode 100644 index 00000000000..28c452814aa --- /dev/null +++ b/test/configCases/html/style-tag-context/webpack.config.js @@ -0,0 +1,14 @@ +"use strict"; + +/** @type {import("../../../../").Configuration} */ +module.exports = { + devtool: false, + target: "web", + output: { + assetModuleFilename: "handled-[name][ext]" + }, + experiments: { + html: true, + css: true + } +}; diff --git a/test/configCases/html/style-tag-no-css/index.js b/test/configCases/html/style-tag-no-css/index.js new file mode 100644 index 00000000000..c4b2a29ff8f --- /dev/null +++ b/test/configCases/html/style-tag-no-css/index.js @@ -0,0 +1,10 @@ +import page from "./page.html"; + +it("should leave inline ` so the rawtext isn't reparsed as HTML. + expect(page).toContain("body { color: red; }"); + expect(page).not.toContain("data:text/css"); +}); diff --git a/test/configCases/html/style-tag-no-css/page.html b/test/configCases/html/style-tag-no-css/page.html new file mode 100644 index 00000000000..c73638f96da --- /dev/null +++ b/test/configCases/html/style-tag-no-css/page.html @@ -0,0 +1,12 @@ + + + +No CSS Test + + + +

Hi

+ + diff --git a/test/configCases/html/style-tag-no-css/webpack.config.js b/test/configCases/html/style-tag-no-css/webpack.config.js new file mode 100644 index 00000000000..2d275504b64 --- /dev/null +++ b/test/configCases/html/style-tag-no-css/webpack.config.js @@ -0,0 +1,10 @@ +"use strict"; + +/** @type {import("../../../../").Configuration} */ +module.exports = { + devtool: false, + target: "web", + experiments: { + html: true + } +}; diff --git a/test/configCases/html/style-tag/__snapshots__/ConfigCacheTest.snap b/test/configCases/html/style-tag/__snapshots__/ConfigCacheTest.snap new file mode 100644 index 00000000000..e3bbe0a92b7 --- /dev/null +++ b/test/configCases/html/style-tag/__snapshots__/ConfigCacheTest.snap @@ -0,0 +1,42 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigCacheTestCases html style-tag exported tests should process inline + + + + + + +

Hi

+ + +" +`; diff --git a/test/configCases/html/style-tag/__snapshots__/ConfigTest.snap b/test/configCases/html/style-tag/__snapshots__/ConfigTest.snap new file mode 100644 index 00000000000..331884d58c8 --- /dev/null +++ b/test/configCases/html/style-tag/__snapshots__/ConfigTest.snap @@ -0,0 +1,42 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigTestCases html style-tag exported tests should process inline + + + + + + +

Hi

+ + +" +`; diff --git a/test/configCases/html/style-tag/index.js b/test/configCases/html/style-tag/index.js new file mode 100644 index 00000000000..75f10065fa0 --- /dev/null +++ b/test/configCases/html/style-tag/index.js @@ -0,0 +1,38 @@ +import page from "./page.html"; + +it("should process inline + + + + + + +

Hi

+ + diff --git a/test/configCases/html/style-tag/pixel.png b/test/configCases/html/style-tag/pixel.png new file mode 100644 index 00000000000..e69de29bb2d diff --git a/test/configCases/html/style-tag/webpack.config.js b/test/configCases/html/style-tag/webpack.config.js new file mode 100644 index 00000000000..28c452814aa --- /dev/null +++ b/test/configCases/html/style-tag/webpack.config.js @@ -0,0 +1,14 @@ +"use strict"; + +/** @type {import("../../../../").Configuration} */ +module.exports = { + devtool: false, + target: "web", + output: { + assetModuleFilename: "handled-[name][ext]" + }, + experiments: { + html: true, + css: true + } +}; diff --git a/test/configCases/html/svg-namespace/__snapshots__/ConfigCacheTest.snap b/test/configCases/html/svg-namespace/__snapshots__/ConfigCacheTest.snap new file mode 100644 index 00000000000..fe20cc41f1b --- /dev/null +++ b/test/configCases/html/svg-namespace/__snapshots__/ConfigCacheTest.snap @@ -0,0 +1,15 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigCacheTestCases html svg-namespace exported tests should rewrite svg-namespace fill/stroke urls but skip html elements 1`] = ` +" + +svg-namespace + +
+ + + + + +" +`; diff --git a/test/configCases/html/svg-namespace/__snapshots__/ConfigTest.snap b/test/configCases/html/svg-namespace/__snapshots__/ConfigTest.snap new file mode 100644 index 00000000000..b9ae59f1d9f --- /dev/null +++ b/test/configCases/html/svg-namespace/__snapshots__/ConfigTest.snap @@ -0,0 +1,15 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigTestCases html svg-namespace exported tests should rewrite svg-namespace fill/stroke urls but skip html elements 1`] = ` +" + +svg-namespace + +
+ + + + + +" +`; diff --git a/test/configCases/html/svg-namespace/icon.svg b/test/configCases/html/svg-namespace/icon.svg new file mode 100644 index 00000000000..b7d6b25aef3 --- /dev/null +++ b/test/configCases/html/svg-namespace/icon.svg @@ -0,0 +1 @@ + diff --git a/test/configCases/html/svg-namespace/index.js b/test/configCases/html/svg-namespace/index.js new file mode 100644 index 00000000000..07ad136a355 --- /dev/null +++ b/test/configCases/html/svg-namespace/index.js @@ -0,0 +1,11 @@ +import page from "./page.html"; + +it("should rewrite svg-namespace fill/stroke urls but skip html elements", () => { + // HTML-namespace div: fill/stroke must NOT be rewritten + expect(page).toContain('fill="url(./icon.svg)"'); + expect(page).toContain('stroke="url(./icon.svg)"'); + // SVG-namespace circle: fill IS rewritten to hashed asset + expect(page).not.toContain('fill="url(./icon.svg)" cx'); + expect(page).toMatch(/fill="url\([a-f0-9]+\.svg\)"/); + expect(page).toMatchSnapshot(); +}); diff --git a/test/configCases/html/svg-namespace/page.html b/test/configCases/html/svg-namespace/page.html new file mode 100644 index 00000000000..ad7ad20d59d --- /dev/null +++ b/test/configCases/html/svg-namespace/page.html @@ -0,0 +1,10 @@ + + +svg-namespace + +
+ + + + + diff --git a/test/configCases/html/svg-namespace/webpack.config.js b/test/configCases/html/svg-namespace/webpack.config.js new file mode 100644 index 00000000000..5b9010cc8df --- /dev/null +++ b/test/configCases/html/svg-namespace/webpack.config.js @@ -0,0 +1,7 @@ +"use strict"; + +/** @type {import("../../../../").Configuration} */ +module.exports = { + target: "web", + experiments: { html: true } +}; diff --git a/test/configCases/html/svg-paint-server-references/__snapshots__/ConfigCacheTest.snap b/test/configCases/html/svg-paint-server-references/__snapshots__/ConfigCacheTest.snap new file mode 100644 index 00000000000..ae9e7cc4cfd --- /dev/null +++ b/test/configCases/html/svg-paint-server-references/__snapshots__/ConfigCacheTest.snap @@ -0,0 +1,25 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigCacheTestCases html svg-paint-server-references exported tests should resolve external SVG paint-server and color-profile references 1`] = ` +" + +SVG paint servers + + + + + + + + + + + + + + + + + +" +`; diff --git a/test/configCases/html/svg-paint-server-references/__snapshots__/ConfigTest.snap b/test/configCases/html/svg-paint-server-references/__snapshots__/ConfigTest.snap new file mode 100644 index 00000000000..4cf56ef4192 --- /dev/null +++ b/test/configCases/html/svg-paint-server-references/__snapshots__/ConfigTest.snap @@ -0,0 +1,25 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigTestCases html svg-paint-server-references exported tests should resolve external SVG paint-server and color-profile references 1`] = ` +" + +SVG paint servers + + + + + + + + + + + + + + + + + +" +`; diff --git a/test/configCases/html/svg-paint-server-references/defs.svg b/test/configCases/html/svg-paint-server-references/defs.svg new file mode 100644 index 00000000000..33a5892dc59 --- /dev/null +++ b/test/configCases/html/svg-paint-server-references/defs.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/test/configCases/html/svg-paint-server-references/index.js b/test/configCases/html/svg-paint-server-references/index.js new file mode 100644 index 00000000000..3e0a751a5ff --- /dev/null +++ b/test/configCases/html/svg-paint-server-references/index.js @@ -0,0 +1,19 @@ +import page from "./page.html"; + +it("should resolve external SVG paint-server and color-profile references", () => { + expect(page).not.toContain("./defs.svg"); + expect(page).not.toContain("./sRGB.icc"); + // linearGradient / radialGradient / pattern / filter href + xlink:href + expect(page).toMatch(//); + expect(page).toMatch( + // + ); + expect(page).toMatch(//); + expect(page).toMatch(//); + // color-profile points at an external ICC profile file + expect(page).toMatch(//); + // Fragment-only references stay untouched + expect(page).toContain(''); + expect(page).toContain('fill="url(#g1)"'); + expect(page).toMatchSnapshot(); +}); diff --git a/test/configCases/html/svg-paint-server-references/page.html b/test/configCases/html/svg-paint-server-references/page.html new file mode 100644 index 00000000000..919bdcc98f6 --- /dev/null +++ b/test/configCases/html/svg-paint-server-references/page.html @@ -0,0 +1,20 @@ + + +SVG paint servers + + + + + + + + + + + + + + + + + diff --git a/test/configCases/html/svg-paint-server-references/sRGB.icc b/test/configCases/html/svg-paint-server-references/sRGB.icc new file mode 100644 index 00000000000..f55b2beefc6 --- /dev/null +++ b/test/configCases/html/svg-paint-server-references/sRGB.icc @@ -0,0 +1 @@ +ICCPROFILE-placeholder diff --git a/test/configCases/html/svg-paint-server-references/webpack.config.js b/test/configCases/html/svg-paint-server-references/webpack.config.js new file mode 100644 index 00000000000..2d275504b64 --- /dev/null +++ b/test/configCases/html/svg-paint-server-references/webpack.config.js @@ -0,0 +1,10 @@ +"use strict"; + +/** @type {import("../../../../").Configuration} */ +module.exports = { + devtool: false, + target: "web", + experiments: { + html: true + } +}; diff --git a/test/configCases/html/svg-presentation-url/__snapshots__/ConfigCacheTest.snap b/test/configCases/html/svg-presentation-url/__snapshots__/ConfigCacheTest.snap new file mode 100644 index 00000000000..983759dddbe --- /dev/null +++ b/test/configCases/html/svg-presentation-url/__snapshots__/ConfigCacheTest.snap @@ -0,0 +1,21 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigCacheTestCases html svg-presentation-url exported tests should resolve url() references in SVG presentation attributes 1`] = ` +" + +SVG presentation url() + + + + + + + + + masked + + + + +" +`; diff --git a/test/configCases/html/svg-presentation-url/__snapshots__/ConfigTest.snap b/test/configCases/html/svg-presentation-url/__snapshots__/ConfigTest.snap new file mode 100644 index 00000000000..5239bdc18f4 --- /dev/null +++ b/test/configCases/html/svg-presentation-url/__snapshots__/ConfigTest.snap @@ -0,0 +1,21 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigTestCases html svg-presentation-url exported tests should resolve url() references in SVG presentation attributes 1`] = ` +" + +SVG presentation url() + + + + + + + + + masked + + + + +" +`; diff --git a/test/configCases/html/svg-presentation-url/index.js b/test/configCases/html/svg-presentation-url/index.js new file mode 100644 index 00000000000..f385a98c03d --- /dev/null +++ b/test/configCases/html/svg-presentation-url/index.js @@ -0,0 +1,13 @@ +import page from "./page.html"; + +it("should resolve url() references in SVG presentation attributes", () => { + expect(page).not.toContain("./paint.svg"); + // Unquoted and quoted url() in fill/stroke/filter/mask are rewritten + expect(page).toMatch(/fill="url\([0-9a-f]+\.svg#grad\)"/); + expect(page).toMatch(/stroke="url\('[0-9a-f]+\.svg#grad'\)"/); + expect(page).toMatch(/filter="url\([0-9a-f]+\.svg#blur\)"/); + expect(page).toMatch(/mask="url\([0-9a-f]+\.svg#m\)"/); + // Internal FuncIRI references stay untouched + expect(page).toContain('clip-path="url(#clip)"'); + expect(page).toMatchSnapshot(); +}); diff --git a/test/configCases/html/svg-presentation-url/page.html b/test/configCases/html/svg-presentation-url/page.html new file mode 100644 index 00000000000..3bc2980655a --- /dev/null +++ b/test/configCases/html/svg-presentation-url/page.html @@ -0,0 +1,16 @@ + + +SVG presentation url() + + + + + + + + + masked + + + + diff --git a/test/configCases/html/svg-presentation-url/paint.svg b/test/configCases/html/svg-presentation-url/paint.svg new file mode 100644 index 00000000000..037db38953b --- /dev/null +++ b/test/configCases/html/svg-presentation-url/paint.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/test/configCases/html/svg-presentation-url/webpack.config.js b/test/configCases/html/svg-presentation-url/webpack.config.js new file mode 100644 index 00000000000..2d275504b64 --- /dev/null +++ b/test/configCases/html/svg-presentation-url/webpack.config.js @@ -0,0 +1,10 @@ +"use strict"; + +/** @type {import("../../../../").Configuration} */ +module.exports = { + devtool: false, + target: "web", + experiments: { + html: true + } +}; diff --git a/test/configCases/html/svg-references/__snapshots__/ConfigCacheTest.snap b/test/configCases/html/svg-references/__snapshots__/ConfigCacheTest.snap new file mode 100644 index 00000000000..c1c844e4649 --- /dev/null +++ b/test/configCases/html/svg-references/__snapshots__/ConfigCacheTest.snap @@ -0,0 +1,19 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigCacheTestCases html svg-references exported tests should resolve external textPath and mpath references 1`] = ` +" + +SVG references + + + + external + local + + + + + + +" +`; diff --git a/test/configCases/html/svg-references/__snapshots__/ConfigTest.snap b/test/configCases/html/svg-references/__snapshots__/ConfigTest.snap new file mode 100644 index 00000000000..a69f0d7756b --- /dev/null +++ b/test/configCases/html/svg-references/__snapshots__/ConfigTest.snap @@ -0,0 +1,19 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigTestCases html svg-references exported tests should resolve external textPath and mpath references 1`] = ` +" + +SVG references + + + + external + local + + + + + + +" +`; diff --git a/test/configCases/html/svg-references/index.js b/test/configCases/html/svg-references/index.js new file mode 100644 index 00000000000..cd0df30cbc3 --- /dev/null +++ b/test/configCases/html/svg-references/index.js @@ -0,0 +1,11 @@ +import page from "./page.html"; + +it("should resolve external textPath and mpath references", () => { + expect(page).not.toContain("./refs.svg"); + expect(page).toMatch(//); + expect(page).toMatch(//); + // Fragment-only references stay untouched + expect(page).toContain('xlink:href="#local-path"'); + expect(page).toContain('xlink:href="#local-motion"'); + expect(page).toMatchSnapshot(); +}); diff --git a/test/configCases/html/svg-references/page.html b/test/configCases/html/svg-references/page.html new file mode 100644 index 00000000000..76c4a829ce4 --- /dev/null +++ b/test/configCases/html/svg-references/page.html @@ -0,0 +1,14 @@ + + +SVG references + + + + external + local + + + + + + diff --git a/test/configCases/html/svg-references/refs.svg b/test/configCases/html/svg-references/refs.svg new file mode 100644 index 00000000000..07e7602cc85 --- /dev/null +++ b/test/configCases/html/svg-references/refs.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/configCases/html/svg-references/webpack.config.js b/test/configCases/html/svg-references/webpack.config.js new file mode 100644 index 00000000000..2d275504b64 --- /dev/null +++ b/test/configCases/html/svg-references/webpack.config.js @@ -0,0 +1,10 @@ +"use strict"; + +/** @type {import("../../../../").Configuration} */ +module.exports = { + devtool: false, + target: "web", + experiments: { + html: true + } +}; diff --git a/test/configCases/html/svg-script-href/__snapshots__/ConfigCacheTest.snap b/test/configCases/html/svg-script-href/__snapshots__/ConfigCacheTest.snap new file mode 100644 index 00000000000..826e9bef04a --- /dev/null +++ b/test/configCases/html/svg-script-href/__snapshots__/ConfigCacheTest.snap @@ -0,0 +1,20 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigCacheTestCases html svg-script-href exported tests should bundle SVG + + + + + + + + +" +`; diff --git a/test/configCases/html/svg-script-href/__snapshots__/ConfigTest.snap b/test/configCases/html/svg-script-href/__snapshots__/ConfigTest.snap new file mode 100644 index 00000000000..889d7fa4b48 --- /dev/null +++ b/test/configCases/html/svg-script-href/__snapshots__/ConfigTest.snap @@ -0,0 +1,20 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigTestCases html svg-script-href exported tests should bundle SVG + + + + + + + + +" +`; diff --git a/test/configCases/html/svg-script-href/entry.js b/test/configCases/html/svg-script-href/entry.js new file mode 100644 index 00000000000..18da2840e09 --- /dev/null +++ b/test/configCases/html/svg-script-href/entry.js @@ -0,0 +1 @@ +module.exports = "svg xlink:href entry"; diff --git a/test/configCases/html/svg-script-href/entry2.js b/test/configCases/html/svg-script-href/entry2.js new file mode 100644 index 00000000000..2e821ed5a6e --- /dev/null +++ b/test/configCases/html/svg-script-href/entry2.js @@ -0,0 +1 @@ +module.exports = "svg href entry"; diff --git a/test/configCases/html/svg-script-href/index.js b/test/configCases/html/svg-script-href/index.js new file mode 100644 index 00000000000..a3481c74df1 --- /dev/null +++ b/test/configCases/html/svg-script-href/index.js @@ -0,0 +1,13 @@ +import page from "./page.html"; + +it("should bundle SVG + + + + + + + + diff --git a/test/configCases/html/svg-script-href/webpack.config.js b/test/configCases/html/svg-script-href/webpack.config.js new file mode 100644 index 00000000000..fa8ab72d430 --- /dev/null +++ b/test/configCases/html/svg-script-href/webpack.config.js @@ -0,0 +1,16 @@ +"use strict"; + +/** @type {import("../../../../").Configuration} */ +module.exports = { + devtool: false, + target: "web", + output: { + chunkFilename: "[name].chunk.js" + }, + optimization: { + chunkIds: "named" + }, + experiments: { + html: true + } +}; diff --git a/test/configCases/html/template-content/__snapshots__/ConfigCacheTest.snap b/test/configCases/html/template-content/__snapshots__/ConfigCacheTest.snap new file mode 100644 index 00000000000..ba23f4a9d33 --- /dev/null +++ b/test/configCases/html/template-content/__snapshots__/ConfigCacheTest.snap @@ -0,0 +1,22 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`ConfigCacheTestCases html template-content exported tests should rewrite asset URLs inside