Full Code of MithrilJS/mithril.js for AI

main bd0ee3fde383 cached
138 files
869.2 KB
260.0k tokens
307 symbols
1 requests
Download .txt
Showing preview only (912K chars total). Download the full file or copy to clipboard to get everything.
Repository: MithrilJS/mithril.js
Branch: main
Commit: bd0ee3fde383
Files: 138
Total size: 869.2 KB

Directory structure:
gitextract_7dh5okin/

├── .deploy.enc
├── .editorconfig
├── .eslintignore
├── .eslintrc.js
├── .gitattributes
├── .github/
│   ├── CODEOWNERS
│   ├── ISSUE_TEMPLATE/
│   │   ├── 1-core.yml
│   │   ├── 2-stream.yml
│   │   └── config.yml
│   ├── PULL_REQUEST_TEMPLATE.md
│   ├── dependabot.yml
│   └── workflows/
│       ├── issue-create.yml
│       ├── notify-release.yml
│       ├── pr-create-release.yml
│       ├── publish-prerelease.yml
│       ├── push-release.yml
│       ├── rollback.yml
│       └── test.yml
├── .gitignore
├── .npmignore
├── LICENSE
├── README.md
├── api/
│   ├── mount-redraw.js
│   ├── router.js
│   └── tests/
│       ├── test-mountRedraw.js
│       ├── test-router.js
│       └── test-routerGetSet.js
├── browser.js
├── docs/
│   ├── code-of-conduct.md
│   ├── contributing.md
│   ├── credits.md
│   ├── recent-changes.md
│   └── releasing.md
├── hyperscript.js
├── index.js
├── mithril.js
├── mount-redraw.js
├── mount.js
├── package.json
├── pathname/
│   ├── build.js
│   ├── compileTemplate.js
│   ├── parse.js
│   └── tests/
│       ├── test-buildPathname.js
│       ├── test-compileTemplate.js
│       └── test-parsePathname.js
├── performance/
│   ├── index.html
│   └── test-perf.js
├── querystring/
│   ├── build.js
│   ├── parse.js
│   └── tests/
│       ├── test-buildQueryString.js
│       └── test-parseQueryString.js
├── redraw.js
├── render/
│   ├── cachedAttrsIsStaticMap.js
│   ├── delayedRemoval.js
│   ├── domFor.js
│   ├── emptyAttrs.js
│   ├── fragment.js
│   ├── hyperscript.js
│   ├── hyperscriptVnode.js
│   ├── render.js
│   ├── tests/
│   │   ├── .eslintrc.js
│   │   ├── manual/
│   │   │   ├── case-handling.html
│   │   │   ├── iframe.html
│   │   │   ├── index.html
│   │   │   ├── minlength-input.html
│   │   │   └── minlength-textarea.html
│   │   ├── test-attributes.js
│   │   ├── test-component.js
│   │   ├── test-createElement.js
│   │   ├── test-createFragment.js
│   │   ├── test-createHTML.js
│   │   ├── test-createNodes.js
│   │   ├── test-createText.js
│   │   ├── test-domFor.js
│   │   ├── test-event.js
│   │   ├── test-fragment.js
│   │   ├── test-hyperscript.js
│   │   ├── test-input.js
│   │   ├── test-normalize.js
│   │   ├── test-normalizeChildren.js
│   │   ├── test-normalizeComponentChildren.js
│   │   ├── test-onbeforeremove.js
│   │   ├── test-onbeforeupdate.js
│   │   ├── test-oncreate.js
│   │   ├── test-oninit.js
│   │   ├── test-onremove.js
│   │   ├── test-onupdate.js
│   │   ├── test-render-hyperscript-integration.js
│   │   ├── test-render.js
│   │   ├── test-textContent.js
│   │   ├── test-trust.js
│   │   ├── test-updateElement.js
│   │   ├── test-updateFragment.js
│   │   ├── test-updateHTML.js
│   │   ├── test-updateNodes.js
│   │   ├── test-updateNodesFuzzer.js
│   │   └── test-updateText.js
│   ├── trust.js
│   └── vnode.js
├── render.js
├── request/
│   ├── request.js
│   └── tests/
│       └── test-request.js
├── request.js
├── route.js
├── scripts/
│   ├── .eslintrc.js
│   ├── _bundler-impl.js
│   ├── bundler-readme.md
│   ├── bundler.js
│   ├── minify-stream.js
│   ├── set-versioned-branch.sh
│   └── tests/
│       └── test-bundler.js
├── stream/
│   ├── stream.js
│   └── tests/
│       ├── test-scan.js
│       ├── test-scanMerge.js
│       └── test-stream.js
├── stream.js
├── test-utils/
│   ├── browserMock.js
│   ├── callAsync.js
│   ├── components.js
│   ├── domMock.js
│   ├── parseURL.js
│   ├── pushStateMock.js
│   ├── tests/
│   │   ├── test-browserMock.js
│   │   ├── test-callAsync.js
│   │   ├── test-components.js
│   │   ├── test-domMock.js
│   │   ├── test-parseURL.js
│   │   ├── test-pushStateMock.js
│   │   ├── test-throttleMock.js
│   │   └── test-xhrMock.js
│   ├── throttleMock.js
│   └── xhrMock.js
├── tests/
│   └── test-api.js
└── util/
    ├── censor.js
    ├── decodeURIComponentSafe.js
    ├── hasOwn.js
    └── tests/
        ├── test-censor.js
        └── test-decodeURIComponentSafe.js

================================================
FILE CONTENTS
================================================

================================================
FILE: .editorconfig
================================================
root = true

[*]
charset = utf-8

[*.{js,json,yml,html,md}]
indent_style = tab
tab_width = 4
trim_trailing_whitespace = true
insert_final_newline = true
end_of_line = lf

[{package.json,.travis.yml,.github/**/*.yml}]
indent_style = space
indent_size = 2


================================================
FILE: .eslintignore
================================================
/coverage
/node_modules
/jsconfig.json
/npm-debug.log
/.vscode
/.DS_Store
/.eslintcache

# These are artifacts from various scripts
/mithril.js
/mithril.min.js
/stream/stream.min.js


================================================
FILE: .eslintrc.js
================================================
"use strict"

module.exports = {
	"env": {
		"browser": true,
		"commonjs": true,
		"es6": true,
		"node": true
	},
	"extends": "eslint:recommended",
	"rules": {
		"accessor-pairs": "error",
		"array-bracket-spacing": [
			"error",
			"never"
		],
		"array-callback-return": "error",
		"arrow-body-style": "error",
		"arrow-parens": "error",
		"arrow-spacing": "error",
		"block-scoped-var": "off",
		"block-spacing": "off",
		"brace-style": "off",
		"callback-return": "off",
		"camelcase": [
			"error",
			{
				"properties": "never"
			}
		],
		"comma-dangle": [
			"error",
			"only-multiline"
		],
		"comma-spacing": "off",
		"comma-style": [
			"error",
			"last"
		],
		"complexity": "off",
		"computed-property-spacing": [
			"error",
			"never"
		],
		"consistent-return": "off",
		"consistent-this": "off",
		"curly": "off",
		"default-case": "off",
		"dot-location": [
			"error",
			"property"
		],
		"dot-notation": "off",
		"eol-last": "off",
		"eqeqeq": "off",
		"func-names": "off",
		"func-style": "off",
		"generator-star-spacing": "error",
		"global-require": "error",
		"guard-for-in": "off",
		"handle-callback-err": "error",
		"id-blacklist": "error",
		"id-length": "off",
		"id-match": "error",
		"indent": [
			"warn",
			"tab",
			{
				"outerIIFEBody": 0,
				"SwitchCase": 1
			}
		],
		"init-declarations": "off",
		"jsx-quotes": "error",
		"key-spacing": "off",
		"keyword-spacing": "off",
		"linebreak-style": "off",
		"lines-around-comment": "off",
		"max-depth": "off",
		"max-len": "off",
		"max-nested-callbacks": "error",
		"max-params": "off",
		"max-statements": "off",
		"max-statements-per-line": "off",
		"new-parens": "off",
		"newline-after-var": "off",
		"newline-before-return": "off",
		"newline-per-chained-call": "off",
		"no-alert": "error",
		"no-array-constructor": "error",
		"no-bitwise": "error",
		"no-caller": "error",
		"no-catch-shadow": "off",
		"no-cond-assign": "off",
		"no-confusing-arrow": "error",
		"no-console": "off",
		"no-continue": "off",
		"no-div-regex": "error",
		"no-duplicate-imports": "error",
		"no-else-return": "off",
		"no-empty-function": "off",
		"no-eq-null": "off",
		"no-eval": "error",
		"no-extend-native": "off",
		"no-extra-bind": "error",
		"no-extra-label": "error",
		"no-extra-parens": "off",
		"no-floating-decimal": "error",
		"no-implicit-coercion": "error",
		"no-implicit-globals": "error",
		"no-implied-eval": "error",
		"no-inline-comments": "off",
		"no-invalid-this": "off",
		"no-iterator": "error",
		"no-label-var": "off",
		"no-labels": "off",
		"no-lone-blocks": "error",
		"no-lonely-if": "off",
		"no-loop-func": "off",
		"no-magic-numbers": "off",
		"no-mixed-requires": "error",
		"no-multi-spaces": "error",
		"no-multi-str": "error",
		"no-multiple-empty-lines": "error",
		"no-native-reassign": "error",
		"no-negated-condition": "off",
		"no-nested-ternary": "off",
		"no-new": "off",
		"no-new-func": "off",
		"no-new-object": "error",
		"no-new-require": "error",
		"no-new-wrappers": "error",
		"no-octal-escape": "error",
		"no-param-reassign": "off",
		"no-path-concat": "off",
		"no-plusplus": "off",
		"no-process-env": "error",
		"no-process-exit": "error",
		"no-proto": "error",
		"no-redeclare": "off",
		"no-restricted-globals": "error",
		"no-restricted-imports": "error",
		"no-restricted-modules": "error",
		"no-restricted-syntax": "error",
		"no-return-assign": "off",
		"no-script-url": "error",
		"no-self-compare": "error",
		"no-sequences": "off",
		"no-shadow": "off",
		"no-shadow-restricted-names": "error",
		"no-spaced-func": "error",
		"no-sync": "off",
		"no-ternary": "off",
		"no-throw-literal": "off",
		"no-trailing-spaces": [
			"error",
			{
				"skipBlankLines": true
			}
		],
		"no-undef-init": "error",
		"no-undefined": "off",
		"no-underscore-dangle": "off",
		"no-unmodified-loop-condition": "error",
		"no-unneeded-ternary": "error",
		"no-unused-expressions": "off",
		"no-use-before-define": "off",
		"no-useless-call": "error",
		"no-useless-concat": "error",
		"no-useless-constructor": "error",
		"no-useless-escape": "off",
		"no-var": "off",
		"no-void": "off",
		"no-warning-comments": "off",
		"no-whitespace-before-property": "error",
		"no-with": "error",
		"object-curly-spacing": [
			"error",
			"never"
		],
		"object-shorthand": "off",
		"one-var": "off",
		"one-var-declaration-per-line": "off",
		"operator-assignment": [
			"error",
			"always"
		],
		"operator-linebreak": "off",
		"padded-blocks": "off",
		"prefer-arrow-callback": "off",
		"prefer-const": "error",
		"prefer-reflect": "off",
		"prefer-rest-params": "off",
		"prefer-spread": "off",
		"prefer-template": "off",
		"quote-props": "off",
		"quotes": [
			"error",
			"double",
			{"avoidEscape": true}
		],
		"radix": [
			"error",
			"always"
		],
		"require-jsdoc": "off",
		"require-yield": "error",
		"semi": "off",
		"semi-spacing": "off",
		"sort-imports": "error",
		"sort-vars": "off",
		"space-before-blocks": "off",
		"space-before-function-paren": "off",
		"space-in-parens": [
			"error",
			"never"
		],
		"space-infix-ops": "off",
		"space-unary-ops": "error",
		"spaced-comment": "off",
		"strict": ["error", "global"],
		"template-curly-spacing": "error",
		"valid-jsdoc": "off",
		"vars-on-top": "off",
		"wrap-iife": "off",
		"wrap-regex": "error",
		"yield-star-spacing": "error",
		"yoda": "off"
	},
	"root": true
};


================================================
FILE: .gitattributes
================================================
* text=auto
/mithril.js binary
/mithril.min.js binary

# Assets
*.png binary
*.svg binary
*.ico binary


================================================
FILE: .github/CODEOWNERS
================================================
* @MithrilJS/Committers
/.github/ @MithrilJS/Admins


================================================
FILE: .github/ISSUE_TEMPLATE/1-core.yml
================================================
name: '🐛 Framework Bug'
description: Report a bug in Mithril.js core
assignees: dead-claudia
labels:
- 'Area: Core'
body:
- type: checkboxes
  attributes:
    label: Is there an existing issue for this?
    description: Please search to see if an issue already exists for the bug you encountered.
    options:
    - label: I have searched the existing issues
      required: true
- type: input
  attributes:
    label: Mithril.js Version
    description: |
      Provide the exact version of Mithril.js you're experiencing these issues with. This
      matters, even if it's really old like version 0.1.0. Do note that bugs in older
      versions are commonly fixed in newer versions, so you should try to test it
      against the latest version if you can.
  validations:
    required: true
- type: textarea
  attributes:
    label: Browser and OS
    description: |
      Provide the name and version of both the browser and operating system you're
      experiencing these issues with. If it's multiple, feel free to list multiple.
      This matters, even if it's super ancient.
  validations:
    required: true
- type: textarea
  attributes:
    label: Project
    description: |
      (Optional) Provide a link to your project, if it happens to be open source or if
      you created a repo somewhere that we can look into further. If it's spread across
      multiple repos or projects, feel free to list them all.
- type: textarea
  attributes:
    label: Code
    description: |
      What did you try? What code is causing the unexpected behavior? Make sure to
      try to reduce your code as best as you can while still reproducing the bug, so
      we can more accurately determine the cause. Ideally, it should just be a bunch
      of Mithril.js calls with virtually no logic at all, but it's sufficient to just
      remove unrelated network calls, attributes, and the like.

      In addition, make sure the bug still persists with the latest version of
      Mithril. If it's an older version, the bug may have already been fixed.

      If you'd prefer, replace this code block with a link to a code playground like
      any of these:

      - Flems <https://flems.io/mithril> (stores everything in URL hash)
      - JSFiddle <https://jsfiddle.net>
      - CodePen <https://codepen.io>
      - JSBin <https://jsbin.com>
      - Plunker <https://plnkr.co>
      - Glitch <https://glitch.com> (supports backend)
      - CodeSandbox <https://codesandbox.io> (supports backend)

      Or if it's a remote development project on your own server, feel free to provide
      that if it's serving unminified code we can look at.

      If it's a closed-source repo, it's okay to censor names and pull out irrelevant
      logic - we'd rather not sign NDAs just to see the code you're having trouble
      with. We do still need code of some kind that triggers the bug you're running
      into.
    render: javascript
  validations:
    required: true
- type: textarea
  attributes:
    label: Steps to Reproduce
    description: |
      What steps need to be taken to reproduce this behavior? Please include things
      like specific data that need typed in, specific buttons that need clicked, and
      so on.
    placeholder: |
      1.
      2.
      3.
      4.
  validations:
    required: true
- type: textarea
  attributes:
    label: Expected Behavior
    description: |
      What did you expect to happen?

      - An alert to pop up?
      - A specific thing to be logged?

      Please be very specific here.
  validations:
    required: true
- type: textarea
  attributes:
    label: Observed Behavior
    description: |
      What actually happened?

      - The alert never showed?
      - The wrong thing was logged?

      Please be very specific here.
  validations:
    required: true
- type: textarea
  attributes:
    label: Context
    description: |
      (Optional) How is this issue affecting you? What are you trying to do? Providing
      us context helps us reach a solution that best fits your particular needs.


================================================
FILE: .github/ISSUE_TEMPLATE/2-stream.yml
================================================
name: '🌊 Mithril.js Streams bug'
description: Report an issue with Mithril.js's Streams module
assignees: dead-claudia
labels:
- 'Area: Stream'
body:
- type: checkboxes
  attributes:
    label: Is there an existing issue for this?
    description: Please search to see if an issue already exists for the bug you encountered.
    options:
    - label: I have searched the existing issues
      required: true
- type: input
  attributes:
    label: Mithril.js Version
    description: |
      Provide the exact version of Mithril.js you're experiencing these issues with. This
      matters, even if it's really old like version 0.1.0. Do note that bugs in older
      versions are commonly fixed in newer versions, so you should try to test it
      against the latest version if you can.
  validations:
    required: true
- type: textarea
  attributes:
    label: Browser and OS
    description: |
      Provide the name and version of both the browser and operating system you're
      experiencing these issues with. If it's multiple, feel free to list multiple.
      This matters, even if it's super ancient.
  validations:
    required: true
- type: textarea
  attributes:
    label: Project
    description: |
      (Optional) Provide a link to your project, if it happens to be open source or if
      you created a repo somewhere that we can look into further. If it's spread across
      multiple repos or projects, feel free to list them all.
- type: textarea
  attributes:
    label: Code
    description: |
      What did you try? What code is causing the unexpected behavior? Make sure to
      try to reduce your code as best as you can while still reproducing the bug, so
      we can more accurately determine the cause. Ideally, it should just be a bunch
      of Mithril.js calls with virtually no logic at all, but it's sufficient to just
      remove unrelated network calls, attributes, and the like.

      In addition, make sure the bug still persists with the latest version of
      Mithril. If it's an older version, the bug may have already been fixed.

      If you'd prefer, replace this code block with a link to a code playground like
      any of these:

      - Flems <https://flems.io/mithril> (stores everything in URL hash)
      - JSFiddle <https://jsfiddle.net>
      - CodePen <https://codepen.io>
      - JSBin <https://jsbin.com>
      - Plunker <https://plnkr.co>
      - Glitch <https://glitch.com> (supports backend)
      - CodeSandbox <https://codesandbox.io> (supports backend)

      Or if it's a remote development project on your own server, feel free to provide
      that if it's serving unminified code we can look at.

      If it's a closed-source repo, it's okay to censor names and pull out irrelevant
      logic - we'd rather not sign NDAs just to see the code you're having trouble
      with. We do still need code of some kind that triggers the bug you're running
      into.
    render: javascript
  validations:
    required: true
- type: textarea
  attributes:
    label: Steps to Reproduce
    description: |
      What steps need to be taken to reproduce this behavior? Please include things
      like specific data that need typed in, specific buttons that need clicked, and
      so on.
    placeholder: |
      1.
      2.
      3.
      4.
  validations:
    required: true
- type: textarea
  attributes:
    label: Expected Behavior
    description: |
      What did you expect to happen?

      - An alert to pop up?
      - A specific thing to be logged?

      Please be very specific here.
  validations:
    required: true
- type: textarea
  attributes:
    label: Observed Behavior
    description: |
      What actually happened?

      - The alert never showed?
      - The wrong thing was logged?

      Please be very specific here.
  validations:
    required: true
- type: textarea
  attributes:
    label: Context
    description: |
      (Optional) How is this issue affecting you? What are you trying to do? Providing
      us context helps us reach a solution that best fits your particular needs.


================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
blank_issues_enabled: true
contact_links:
  - name: ℹ Questions, Ideas, and Discussions
    url: https://github.com/MithrilJS/mithril.js/discussions
    about: |
      Got a question on how to use Mithril.js? Have a fancy idea of how we could do better? Check
      out our discussions forum!
  - name: ℹ Show and Tell
    url: https://github.com/MithrilJS/mithril.js/discussions/new?category=show-and-tell
    about: |
      Got something to show off? Made something so cool, you just have to tell the world? Let us
      know here in our discussions forum!
  - name: 🚀 Feature Request or Enhancement
    url: https://github.com/MithrilJS/mithril.js/discussions/new?category=ideas
    about: Got a feature request? Let us know here! We do those in our discussion forum.
  - name: 📄 Documentation issue
    url: https://github.com/MithrilJS/docs
    about: Found an issue with our documentation? File that in our docs repo instead.
  - name: 💻 Zulip Chat
    url: https://mithril.zulipchat.com/
    about: Not sure about something? Just want to hang out? Come over to our Zulip chat!


================================================
FILE: .github/PULL_REQUEST_TEMPLATE.md
================================================
<!--- Provide a general summary of your changes in the Title above -->

## Description
<!--- Describe your changes in detail -->

## Motivation and Context
<!--- Why is this change required? What problem does it solve? -->
<!--- If it fixes an open issue, please link to the issue here. -->

## How Has This Been Tested?
<!--- Please describe in detail how you tested your changes. -->
<!--- Include details of your testing environment, and the tests you ran to -->
<!--- see how your change affects other areas of the code, etc. -->

## Types of changes
<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to change)

## Checklist
<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->
- [ ] My code follows the code style of this project.
- [ ] I have added tests to cover my changes.
- [ ] All new and existing tests passed.
- [ ] My change requires a documentation update, and I've opened a pull request to update it already:
- [ ] I have read https://mithril.js.org/contributing.html.


================================================
FILE: .github/dependabot.yml
================================================
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file

version: 2
updates:
  - package-ecosystem: "npm" # See documentation for possible values
    directory: "/" # Location of package manifests
    schedule:
      interval: daily
    groups:
      security:
        applies-to: security-updates
        patterns: ['*']
      normal:
        applies-to: version-updates
        patterns: ['*']
  - package-ecosystem: github-actions
    directory: /
    schedule:
      interval: daily
    groups:
      security:
        applies-to: security-updates
        patterns: ['*']
      normal:
        applies-to: version-updates
        patterns: ['*']


================================================
FILE: .github/workflows/issue-create.yml
================================================
name: Ping triage on issue create
on:
  issues:
    types: [opened]
  pull_request_target:
    types: [opened]
jobs:
  notify_triage:
    uses: MithrilJS/infra/.github/workflows/notify-triage.yml@main
    secrets: inherit


================================================
FILE: .github/workflows/notify-release.yml
================================================
name: Notify release

on:
  push:
    tags: ['v*']

jobs:
  update:
    runs-on: ubuntu-latest
    steps:
    - name: Send workflow dispatch to docs
      run: gh --repo MithrilJS/docs workflow run package-update.yml -f package=mithril
      env:
        GH_TOKEN: ${{ secrets.DOCS_UPDATE_TOKEN }}


================================================
FILE: .github/workflows/pr-create-release.yml
================================================
name: Warn on opening a PR to `release`
on:
  pull_request_target:
    types: [opened]
    branches: [release]
permissions:
  issues: write
jobs:
  comment:
    # Don't auto-close actual release PRs
    if: ${{github.actor != 'JAForbes' || !contains(github.event.issue.title, 'Release - v')}}
    uses: MithrilJS/infra/.github/workflows/reject-pr.yml@main
    secrets: inherit


================================================
FILE: .github/workflows/publish-prerelease.yml
================================================
name: Publish prerelease and update PR

on:
  workflow_call:
  workflow_dispatch:

jobs:
  update-pr:
    concurrency: prr:pre-release
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v5
    - uses: actions/setup-node@v6
      with:
        node-version: 20
    - run: npm ci
    - run: npm run build
    - run: npx pr-release pr --verbose --target release --source main --compact --verbose --minimize-semver-change
      env:
        GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
    # The following will publish a prerelease to npm
    - run: echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > ~/.npmrc
      name: Setup NPM Auth
      env:
        NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
    - run: npx pr-release infer-prerelease --preid=next --target release --source main --verbose --publish --minimize-semver-change
      name: Publish

================================================
FILE: .github/workflows/push-release.yml
================================================
name: Create release when pushing to `release`

on:
  push:
    branches: [release]
  workflow_dispatch:

concurrency: merge-release

jobs:
  merge:
    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v5
      with:
        ref: main
    - uses: actions/setup-node@v6
      with:
        node-version: 20
    - run: npm ci
    - run: npm run build
    - run: echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > ~/.npmrc
      name: Setup NPM Auth
      env:
        NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
    - run: npx pr-release merge --target release --source main --commit --force --clean --changelog ./docs/recent-changes.md --compact --minimize-semver-change --prerelease="npm publish"
      env:
        GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
    - run: bash scripts/set-versioned-branch.sh release


================================================
FILE: .github/workflows/rollback.yml
================================================
name: rollback

on:
  workflow_dispatch:

concurrency: prr:deploy

jobs:
  pr:
    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v5
    - uses: actions/setup-node@v6
      with:
        node-version: 20
    - run: npm ci
    - run: npm run build
    - run: npx pr-release rollback --verbose --target release --source main --verbose --ignore 'package*' --ignore docs/changelog.md --ignore docs/recent-changes.md
      env:
        GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
    - run: bash scripts/set-versioned-branch.sh release


================================================
FILE: .github/workflows/test.yml
================================================
name: Test and maybe release

on:
  pull_request_target:
    branches: [ main ]
  push:
    branches: [ main ]
  workflow_dispatch:

jobs:
  run-tests:
    uses: MithrilJS/infra/.github/workflows/run-tests.yml@main
    with:
      test-node: true
      all-versions: true
    permissions:
      actions: write
      contents: read

  publish-prerelease:
    needs: run-tests
    if: ${{ github.event_name == 'push' }}
    concurrency: prr:pre-release
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v5
    - uses: actions/setup-node@v6
      with:
        node-version: 20
    - run: npm ci
    - run: npm run build
    - run: npx pr-release pr --verbose --target release --source main --compact --verbose --minimize-semver-change
      env:
        GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
    # The following will publish a prerelease to npm
    - run: echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > ~/.npmrc
      name: Setup NPM Auth
      env:
        NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
    - run: npx pr-release infer-prerelease --preid=next --target release --source main --verbose --publish --minimize-semver-change
      name: Publish


================================================
FILE: .gitignore
================================================
/coverage
/node_modules
/jsconfig.json
/npm-debug.log
/.vscode
/.DS_Store
/.eslintcache


================================================
FILE: .npmignore
================================================
# Development-specific files
/.deploy.env
/.editorconfig
/.eslintrc.js
/.eslintcache
/.eslintignore
/.gitattributes
/.gitignore
/.travis.yml
/yarn.lock
/scripts/

# Exclude all directories named "tests" as it's used only for tests. This is
# intentionally not prefixed with a `/` because it applies to both the root and
# subdirectories.
tests/

# Mithril.js' mocks are for internal use only, and it's wholly undocumented for a
# reason. I've already gotten way too many complaints over users' tests breaking
# from changes to it in patch releases. Let's force people to finally stop using
# them.
/test-utils/


================================================
FILE: LICENSE
================================================
The MIT License (MIT)

Copyright (c) 2017 Leo Horie

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
# Mithril.js

[![npm Version](https://img.shields.io/npm/v/mithril.svg)](https://www.npmjs.com/package/mithril) &nbsp;
[![License](https://img.shields.io/npm/l/mithril.svg)](https://github.com/MithrilJS/mithril.js/blob/main/LICENSE) &nbsp;
[![npm Downloads](https://img.shields.io/npm/dm/mithril.svg)](https://www.npmjs.com/package/mithril) &nbsp;
[![Build Status](https://img.shields.io/github/actions/workflow/status/MithrilJS/mithril.js/.github%2Fworkflows%2Ftest.yml?branch=main&event=push)](https://github.com/MithrilJS/mithril.js/actions) &nbsp;
[![Donate at OpenCollective](https://img.shields.io/opencollective/all/mithriljs.svg?colorB=brightgreen)](https://opencollective.com/mithriljs) &nbsp;
[![Zulip, join chat](https://img.shields.io/badge/zulip-join_chat-brightgreen.svg)](https://mithril.zulipchat.com/)

- [What is Mithril.js?](#what-is-mithriljs)
- [Installation](#installation)
- [Documentation](#documentation)
- [Getting Help](#getting-help)
- [Contributing](#contributing)

## What is Mithril.js?

A modern client-side JavaScript framework for building Single Page Applications. It's small (<!-- size -->8.93 KB<!-- /size --> gzipped), fast and provides routing and XHR utilities out of the box.

Mithril.js is used by companies like Vimeo and Nike, and open source platforms like Lichess 👍.

Mithril.js supports Firefox ESR, and the last two versions of Firefox, Edge, Safari, and Chrome. No polyfills required. 👌

## Installation

### CDN

```html
<!-- Development: whichever you prefer -->
<script src="https://unpkg.com/mithril/mithril.js"></script>
<script src="https://cdn.jsdelivr.net/npm/mithril/mithril.js"></script>

<!-- Production: whichever you prefer -->
<script src="https://unpkg.com/mithril/mithril.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/mithril/mithril.min.js"></script>
```

### npm

```bash
npm install mithril --save
```

The ["Getting started" guide](https://mithril.js.org/#getting-started) is a good place to start learning how to use Mithril.js.

TypeScript type definitions are available from DefinitelyTyped. They can be installed with:

```bash
$ npm install @types/mithril --save-dev
```

## Documentation

Documentation lives on [mithril.js.org](https://mithril.js.org).

You may be interested in the [API Docs](https://mithril.js.org/api.html), a [Simple Application](https://mithril.js.org/simple-application.html), or perhaps some [Examples](https://mithril.js.org/examples.html).

## Getting Help

Mithril.js has an active & welcoming community on [Zulip](https://mithril.zulipchat.com/), or feel free to ask questions on [Stack Overflow](https://stackoverflow.com/questions/tagged/mithril.js) using the `mithril.js` tag.

## Contributing

There's a [Contributing FAQ](https://mithril.js.org/contributing.html) on the Mithril.js site that hopefully helps, but if not definitely hop into the [Zulip stream](https://mithril.zulipchat.com/) and ask away!

---

Thanks for reading!

🎁


================================================
FILE: api/mount-redraw.js
================================================
"use strict"

var Vnode = require("../render/vnode")

module.exports = function(render, schedule, console) {
	var subscriptions = []
	var pending = false
	var offset = -1

	function sync() {
		for (offset = 0; offset < subscriptions.length; offset += 2) {
			try { render(subscriptions[offset], Vnode(subscriptions[offset + 1]), redraw) }
			catch (e) { console.error(e) }
		}
		offset = -1
	}

	function redraw() {
		if (!pending) {
			pending = true
			schedule(function() {
				pending = false
				sync()
			})
		}
	}

	redraw.sync = sync

	function mount(root, component) {
		if (component != null && component.view == null && typeof component !== "function") {
			throw new TypeError("m.mount expects a component, not a vnode.")
		}

		var index = subscriptions.indexOf(root)
		if (index >= 0) {
			subscriptions.splice(index, 2)
			if (index <= offset) offset -= 2
			render(root, [])
		}

		if (component != null) {
			subscriptions.push(root, component)
			render(root, Vnode(component), redraw)
		}
	}

	return {mount: mount, redraw: redraw}
}


================================================
FILE: api/router.js
================================================
"use strict"

var Vnode = require("../render/vnode")
var hyperscript = require("../render/hyperscript")

var decodeURIComponentSafe = require("../util/decodeURIComponentSafe")
var buildPathname = require("../pathname/build")
var parsePathname = require("../pathname/parse")
var compileTemplate = require("../pathname/compileTemplate")
var censor = require("../util/censor")

module.exports = function($window, mountRedraw) {
	var p = Promise.resolve()

	var scheduled = false

	var ready = false
	var hasBeenResolved = false

	var dom, compiled, fallbackRoute

	var currentResolver, component, attrs, currentPath, lastUpdate

	var RouterRoot = {
		onremove: function() {
			ready = hasBeenResolved = false
			$window.removeEventListener("popstate", fireAsync, false)
		},
		view: function() {
			// The route has already been resolved.
			// Therefore, the following early return is not needed.
			// if (!hasBeenResolved) return

			var vnode = Vnode(component, attrs.key, attrs)
			if (currentResolver) return currentResolver.render(vnode)
			// Wrap in a fragment to preserve existing key semantics
			return [vnode]
		},
	}

	var SKIP = route.SKIP = {}

	function resolveRoute() {
		scheduled = false
		// Consider the pathname holistically. The prefix might even be invalid,
		// but that's not our problem.
		var prefix = $window.location.hash
		if (route.prefix[0] !== "#") {
			prefix = $window.location.search + prefix
			if (route.prefix[0] !== "?") {
				prefix = $window.location.pathname + prefix
				if (prefix[0] !== "/") prefix = "/" + prefix
			}
		}
		// This seemingly useless `.concat()` speeds up the tests quite a bit,
		// since the representation is consistently a relatively poorly
		// optimized cons string.
		var path = prefix.concat()
			.replace(/(?:%[a-f89][a-f0-9])+/gim, decodeURIComponentSafe)
			.slice(route.prefix.length)
		var data = parsePathname(path)

		Object.assign(data.params, $window.history.state)

		function reject(e) {
			console.error(e)
			route.set(fallbackRoute, null, {replace: true})
		}

		loop(0)
		function loop(i) {
			for (; i < compiled.length; i++) {
				if (compiled[i].check(data)) {
					var payload = compiled[i].component
					var matchedRoute = compiled[i].route
					var localComp = payload
					var update = lastUpdate = function(comp) {
						if (update !== lastUpdate) return
						if (comp === SKIP) return loop(i + 1)
						component = comp != null && (typeof comp.view === "function" || typeof comp === "function")? comp : "div"
						attrs = data.params, currentPath = path, lastUpdate = null
						currentResolver = payload.render ? payload : null
						if (hasBeenResolved) mountRedraw.redraw()
						else {
							hasBeenResolved = true
							mountRedraw.mount(dom, RouterRoot)
						}
					}
					// There's no understating how much I *wish* I could
					// use `async`/`await` here...
					if (payload.view || typeof payload === "function") {
						payload = {}
						update(localComp)
					}
					else if (payload.onmatch) {
						p.then(function () {
							return payload.onmatch(data.params, path, matchedRoute)
						}).then(update, path === fallbackRoute ? null : reject)
					}
					else update(/* "div" */)
					return
				}
			}

			if (path === fallbackRoute) {
				throw new Error("Could not resolve default route " + fallbackRoute + ".")
			}
			route.set(fallbackRoute, null, {replace: true})
		}
	}

	function fireAsync() {
		if (!scheduled) {
			scheduled = true
			// TODO: just do `mountRedraw.redraw()` here and elide the timer
			// dependency. Note that this will muck with tests a *lot*, so it's
			// not as easy of a change as it sounds.
			setTimeout(resolveRoute)
		}
	}

	function route(root, defaultRoute, routes) {
		if (!root) throw new TypeError("DOM element being rendered to does not exist.")

		compiled = Object.keys(routes).map(function(route) {
			if (route[0] !== "/") throw new SyntaxError("Routes must start with a '/'.")
			if ((/:([^\/\.-]+)(\.{3})?:/).test(route)) {
				throw new SyntaxError("Route parameter names must be separated with either '/', '.', or '-'.")
			}
			return {
				route: route,
				component: routes[route],
				check: compileTemplate(route),
			}
		})
		fallbackRoute = defaultRoute
		if (defaultRoute != null) {
			var defaultData = parsePathname(defaultRoute)

			if (!compiled.some(function (i) { return i.check(defaultData) })) {
				throw new ReferenceError("Default route doesn't match any known routes.")
			}
		}
		dom = root

		$window.addEventListener("popstate", fireAsync, false)

		ready = true

		// The RouterRoot component is mounted when the route is first resolved.
		resolveRoute()
	}
	route.set = function(path, data, options) {
		if (lastUpdate != null) {
			options = options || {}
			options.replace = true
		}
		lastUpdate = null

		path = buildPathname(path, data)
		if (ready) {
			fireAsync()
			var state = options ? options.state : null
			var title = options ? options.title : null
			if (options && options.replace) $window.history.replaceState(state, title, route.prefix + path)
			else $window.history.pushState(state, title, route.prefix + path)
		}
		else {
			$window.location.href = route.prefix + path
		}
	}
	route.get = function() {return currentPath}
	route.prefix = "#!"
	route.Link = {
		view: function(vnode) {
			// Omit the used parameters from the rendered element - they are
			// internal. Also, censor the various lifecycle methods.
			//
			// We don't strip the other parameters because for convenience we
			// let them be specified in the selector as well.
			var child = hyperscript(
				vnode.attrs.selector || "a",
				censor(vnode.attrs, ["options", "params", "selector", "onclick"]),
				vnode.children
			)
			var options, onclick, href

			// Let's provide a *right* way to disable a route link, rather than
			// letting people screw up accessibility on accident.
			//
			// The attribute is coerced so users don't get surprised over
			// `disabled: 0` resulting in a button that's somehow routable
			// despite being visibly disabled.
			if (child.attrs.disabled = Boolean(child.attrs.disabled)) {
				child.attrs.href = null
				child.attrs["aria-disabled"] = "true"
				// If you *really* do want add `onclick` on a disabled link, use
				// an `oncreate` hook to add it.
			} else {
				options = vnode.attrs.options
				onclick = vnode.attrs.onclick
				// Easier to build it now to keep it isomorphic.
				href = buildPathname(child.attrs.href, vnode.attrs.params)
				child.attrs.href = route.prefix + href
				child.attrs.onclick = function(e) {
					var result
					if (typeof onclick === "function") {
						result = onclick.call(e.currentTarget, e)
					} else if (onclick == null || typeof onclick !== "object") {
						// do nothing
					} else if (typeof onclick.handleEvent === "function") {
						onclick.handleEvent(e)
					}

					// Adapted from React Router's implementation:
					// https://github.com/ReactTraining/react-router/blob/520a0acd48ae1b066eb0b07d6d4d1790a1d02482/packages/react-router-dom/modules/Link.js
					//
					// Try to be flexible and intuitive in how we handle links.
					// Fun fact: links aren't as obvious to get right as you
					// would expect. There's a lot more valid ways to click a
					// link than this, and one might want to not simply click a
					// link, but right click or command-click it to copy the
					// link target, etc. Nope, this isn't just for blind people.
					if (
						// Skip if `onclick` prevented default
						result !== false && !e.defaultPrevented &&
						// Ignore everything but left clicks
						(e.button === 0 || e.which === 0 || e.which === 1) &&
						// Let the browser handle `target=_blank`, etc.
						(!e.currentTarget.target || e.currentTarget.target === "_self") &&
						// No modifier keys
						!e.ctrlKey && !e.metaKey && !e.shiftKey && !e.altKey
					) {
						e.preventDefault()
						e.redraw = false
						route.set(href, null, options)
					}
				}
			}
			return child
		},
	}
	route.param = function(key) {
		return attrs && key != null ? attrs[key] : attrs
	}

	return route
}


================================================
FILE: api/tests/test-mountRedraw.js
================================================
"use strict"

// Low-priority TODO: remove the dependency on the renderer here.
var o = require("ospec")
var components = require("../../test-utils/components")
var domMock = require("../../test-utils/domMock")
var throttleMocker = require("../../test-utils/throttleMock")
var mountRedraw = require("../../api/mount-redraw")
var coreRenderer = require("../../render/render")
var h = require("../../render/hyperscript")

o.spec("mount/redraw", function() {
	var root, m, throttleMock, consoleMock, $document, errors
	o.beforeEach(function() {
		var $window = domMock()
		consoleMock = {error: o.spy()}
		throttleMock = throttleMocker()
		root = $window.document.body
		m = mountRedraw(coreRenderer($window), throttleMock.schedule, consoleMock)
		$document = $window.document
		errors = []
	})

	o.afterEach(function() {
		o(consoleMock.error.calls.map(function(c) {
			return c.args[0]
		})).deepEquals(errors)
		o(throttleMock.queueLength()).equals(0)
	})

	o("shouldn't error if there are no renderers", function() {
		m.redraw()
		throttleMock.fire()
	})

	o("schedules correctly", function() {
		var spy = o.spy()

		m.mount(root, {view: spy})
		o(spy.callCount).equals(1)
		m.redraw()
		o(spy.callCount).equals(1)
		throttleMock.fire()
		o(spy.callCount).equals(2)
	})

	o("should run a single renderer entry", function() {
		var spy = o.spy()

		m.mount(root, {view: spy})

		o(spy.callCount).equals(1)

		m.redraw()
		m.redraw()
		m.redraw()

		o(spy.callCount).equals(1)
		throttleMock.fire()
		o(spy.callCount).equals(2)
	})

	o("should run all renderer entries", function() {
		var el1 = $document.createElement("div")
		var el2 = $document.createElement("div")
		var el3 = $document.createElement("div")
		var spy1 = o.spy()
		var spy2 = o.spy()
		var spy3 = o.spy()

		m.mount(el1, {view: spy1})
		m.mount(el2, {view: spy2})
		m.mount(el3, {view: spy3})

		m.redraw()

		o(spy1.callCount).equals(1)
		o(spy2.callCount).equals(1)
		o(spy3.callCount).equals(1)

		m.redraw()

		o(spy1.callCount).equals(1)
		o(spy2.callCount).equals(1)
		o(spy3.callCount).equals(1)

		throttleMock.fire()

		o(spy1.callCount).equals(2)
		o(spy2.callCount).equals(2)
		o(spy3.callCount).equals(2)
	})

	o("should not redraw when mounting another root", function() {
		var el1 = $document.createElement("div")
		var el2 = $document.createElement("div")
		var el3 = $document.createElement("div")
		var spy1 = o.spy()
		var spy2 = o.spy()
		var spy3 = o.spy()

		m.mount(el1, {view: spy1})
		o(spy1.callCount).equals(1)
		o(spy2.callCount).equals(0)
		o(spy3.callCount).equals(0)

		m.mount(el2, {view: spy2})
		o(spy1.callCount).equals(1)
		o(spy2.callCount).equals(1)
		o(spy3.callCount).equals(0)

		m.mount(el3, {view: spy3})
		o(spy1.callCount).equals(1)
		o(spy2.callCount).equals(1)
		o(spy3.callCount).equals(1)
	})

	o("should stop running after mount null", function() {
		var spy = o.spy()

		m.mount(root, {view: spy})
		o(spy.callCount).equals(1)
		m.mount(root, null)

		m.redraw()

		o(spy.callCount).equals(1)
		throttleMock.fire()
		o(spy.callCount).equals(1)
	})

	o("should stop running after mount undefined", function() {
		var spy = o.spy()

		m.mount(root, {view: spy})
		o(spy.callCount).equals(1)
		m.mount(root, undefined)

		m.redraw()

		o(spy.callCount).equals(1)
		throttleMock.fire()
		o(spy.callCount).equals(1)
	})

	o("should stop running after mount no arg", function() {
		var spy = o.spy()

		m.mount(root, {view: spy})
		o(spy.callCount).equals(1)
		m.mount(root)

		m.redraw()

		o(spy.callCount).equals(1)
		throttleMock.fire()
		o(spy.callCount).equals(1)
	})

	o("should invoke remove callback on unmount", function() {
		var spy = o.spy()
		var onremove = o.spy()

		m.mount(root, {view: spy, onremove: onremove})
		o(spy.callCount).equals(1)
		m.mount(root)

		o(spy.callCount).equals(1)
		o(onremove.callCount).equals(1)
	})

	o("should stop running after unsubscribe, even if it occurs after redraw is requested", function() {
		var spy = o.spy()

		m.mount(root, {view: spy})
		o(spy.callCount).equals(1)
		m.redraw()
		m.mount(root)

		o(spy.callCount).equals(1)
		throttleMock.fire()
		o(spy.callCount).equals(1)
	})

	o("does nothing on invalid unmount", function() {
		var spy = o.spy()

		m.mount(root, {view: spy})
		o(spy.callCount).equals(1)

		m.mount(null)
		m.redraw()
		throttleMock.fire()
		o(spy.callCount).equals(2)
	})

	o("redraw.sync() redraws all roots synchronously", function() {
		var el1 = $document.createElement("div")
		var el2 = $document.createElement("div")
		var el3 = $document.createElement("div")
		var spy1 = o.spy()
		var spy2 = o.spy()
		var spy3 = o.spy()

		m.mount(el1, {view: spy1})
		m.mount(el2, {view: spy2})
		m.mount(el3, {view: spy3})

		o(spy1.callCount).equals(1)
		o(spy2.callCount).equals(1)
		o(spy3.callCount).equals(1)

		m.redraw.sync()

		o(spy1.callCount).equals(2)
		o(spy2.callCount).equals(2)
		o(spy3.callCount).equals(2)

		m.redraw.sync()

		o(spy1.callCount).equals(3)
		o(spy2.callCount).equals(3)
		o(spy3.callCount).equals(3)
	})


	o("throws on invalid component", function() {
		o(function() { m.mount(root, {}) }).throws(TypeError)
	})

	o("skips roots that were synchronously unsubscribed before they were visited", function() {
		var calls = []
		var root1 = $document.createElement("div")
		var root2 = $document.createElement("div")
		var root3 = $document.createElement("div")

		m.mount(root1, {
			onbeforeupdate: function() {
				m.mount(root2, null)
			},
			view: function() { calls.push("root1") },
		})
		m.mount(root2, {view: function() { calls.push("root2") }})
		m.mount(root3, {view: function() { calls.push("root3") }})
		o(calls).deepEquals([
			"root1", "root2", "root3",
		])

		m.redraw.sync()
		o(calls).deepEquals([
			"root1", "root2", "root3",
			"root1", "root3",
		])
	})

	o("keeps its place when synchronously unsubscribing previously visited roots", function() {
		var calls = []
		var root1 = $document.createElement("div")
		var root2 = $document.createElement("div")
		var root3 = $document.createElement("div")

		m.mount(root1, {view: function() { calls.push("root1") }})
		m.mount(root2, {
			onbeforeupdate: function() {
				m.mount(root1, null)
			},
			view: function() { calls.push("root2") },
		})
		m.mount(root3, {view: function() { calls.push("root3") }})
		o(calls).deepEquals([
			"root1", "root2", "root3",
		])

		m.redraw.sync()
		o(calls).deepEquals([
			"root1", "root2", "root3",
			"root1", "root2", "root3",
		])
	})

	o("keeps its place when synchronously unsubscribing previously visited roots in the face of errors", function() {
		errors = ["fail"]
		var calls = []
		var root1 = $document.createElement("div")
		var root2 = $document.createElement("div")
		var root3 = $document.createElement("div")

		m.mount(root1, {view: function() { calls.push("root1") }})
		m.mount(root2, {
			onbeforeupdate: function() {
				m.mount(root1, null)
				throw "fail"
			},
			view: function() { calls.push("root2") },
		})
		m.mount(root3, {view: function() { calls.push("root3") }})
		o(calls).deepEquals([
			"root1", "root2", "root3",
		])

		m.redraw.sync()
		o(calls).deepEquals([
			"root1", "root2", "root3",
			"root1", "root3",
		])
	})

	o("keeps its place when synchronously unsubscribing the current root", function() {
		var calls = []
		var root1 = $document.createElement("div")
		var root2 = $document.createElement("div")
		var root3 = $document.createElement("div")

		m.mount(root1, {view: function() { calls.push("root1") }})
		m.mount(root2, {
			onbeforeupdate: function() {
				try { m.mount(root2, null) } catch (e) { calls.push([e.constructor, e.message]) }
			},
			view: function() { calls.push("root2") },
		})
		m.mount(root3, {view: function() { calls.push("root3") }})
		o(calls).deepEquals([
			"root1", "root2", "root3",
		])

		m.redraw.sync()
		o(calls).deepEquals([
			"root1", "root2", "root3",
			"root1", [TypeError, "Node is currently being rendered to and thus is locked."], "root2", "root3",
		])
	})

	o("keeps its place when synchronously unsubscribing the current root in the face of an error", function() {
		errors = [
			[TypeError, "Node is currently being rendered to and thus is locked."],
		]
		var calls = []
		var root1 = $document.createElement("div")
		var root2 = $document.createElement("div")
		var root3 = $document.createElement("div")

		m.mount(root1, {view: function() { calls.push("root1") }})
		m.mount(root2, {
			onbeforeupdate: function() {
				try { m.mount(root2, null) } catch (e) { throw [e.constructor, e.message] }
			},
			view: function() { calls.push("root2") },
		})
		m.mount(root3, {view: function() { calls.push("root3") }})
		o(calls).deepEquals([
			"root1", "root2", "root3",
		])

		m.redraw.sync()
		o(calls).deepEquals([
			"root1", "root2", "root3",
			"root1", "root3",
		])
	})

	components.forEach(function(cmp){
		o.spec(cmp.kind, function(){
			var createComponent = cmp.create

			o("throws on invalid `root` DOM node", function() {
				o(function() {
					m.mount(null, createComponent({view: function() {}}))
				}).throws(TypeError)
			})

			o("renders into `root` synchronously", function() {
				m.mount(root, createComponent({
					view: function() {
						return h("div")
					}
				}))

				o(root.firstChild.nodeName).equals("DIV")
			})

			o("mounting null unmounts", function() {
				m.mount(root, createComponent({
					view: function() {
						return h("div")
					}
				}))

				m.mount(root, null)

				o(root.childNodes.length).equals(0)
			})

			o("Mounting a second root doesn't cause the first one to redraw", function() {
				var root1 = $document.createElement("div")
				var root2 = $document.createElement("div")
				var view = o.spy()

				m.mount(root1, createComponent({view: view}))
				o(view.callCount).equals(1)

				m.mount(root2, createComponent({view: function() {}}))

				o(view.callCount).equals(1)

				throttleMock.fire()
				o(view.callCount).equals(1)
			})

			o("redraws on events", function() {
				var onupdate = o.spy()
				var oninit = o.spy()
				var onclick = o.spy()
				var e = $document.createEvent("MouseEvents")

				e.initEvent("click", true, true)

				m.mount(root, createComponent({
					view: function() {
						return h("div", {
							oninit: oninit,
							onupdate: onupdate,
							onclick: onclick,
						})
					}
				}))

				root.firstChild.dispatchEvent(e)

				o(oninit.callCount).equals(1)
				o(onupdate.callCount).equals(0)

				o(onclick.callCount).equals(1)
				o(onclick.this).equals(root.firstChild)
				o(onclick.args[0].type).equals("click")
				o(onclick.args[0].target).equals(root.firstChild)

				throttleMock.fire()

				o(onupdate.callCount).equals(1)
			})

			o("redraws several mount points on events", function() {
				var onupdate0 = o.spy()
				var oninit0 = o.spy()
				var onclick0 = o.spy()
				var onupdate1 = o.spy()
				var oninit1 = o.spy()
				var onclick1 = o.spy()

				var root1 = $document.createElement("div")
				var root2 = $document.createElement("div")
				var e = $document.createEvent("MouseEvents")

				e.initEvent("click", true, true)

				m.mount(root1, createComponent({
					view: function() {
						return h("div", {
							oninit: oninit0,
							onupdate: onupdate0,
							onclick: onclick0,
						})
					}
				}))

				o(oninit0.callCount).equals(1)
				o(onupdate0.callCount).equals(0)

				m.mount(root2, createComponent({
					view: function() {
						return h("div", {
							oninit: oninit1,
							onupdate: onupdate1,
							onclick: onclick1,
						})
					}
				}))

				o(oninit1.callCount).equals(1)
				o(onupdate1.callCount).equals(0)

				root1.firstChild.dispatchEvent(e)
				o(onclick0.callCount).equals(1)
				o(onclick0.this).equals(root1.firstChild)

				throttleMock.fire()

				o(onupdate0.callCount).equals(1)
				o(onupdate1.callCount).equals(1)

				root2.firstChild.dispatchEvent(e)

				o(onclick1.callCount).equals(1)
				o(onclick1.this).equals(root2.firstChild)

				throttleMock.fire()

				o(onupdate0.callCount).equals(2)
				o(onupdate1.callCount).equals(2)
			})

			o("event handlers can skip redraw", function() {
				var onupdate = o.spy(function(){
					throw new Error("This shouldn't have been called")
				})
				var oninit = o.spy()
				var e = $document.createEvent("MouseEvents")

				e.initEvent("click", true, true)

				m.mount(root, createComponent({
					view: function() {
						return h("div", {
							oninit: oninit,
							onupdate: onupdate,
							onclick: function(e) {
								e.redraw = false
							}
						})
					}
				}))

				root.firstChild.dispatchEvent(e)

				o(oninit.callCount).equals(1)
				o(e.redraw).equals(false)

				throttleMock.fire()

				o(onupdate.callCount).equals(0)
				o(e.redraw).equals(false)
			})

			o("redraws when the render function is run", function() {
				var onupdate = o.spy()
				var oninit = o.spy()

				m.mount(root, createComponent({
					view: function() {
						return h("div", {
							oninit: oninit,
							onupdate: onupdate
						})
					}
				}))

				o(oninit.callCount).equals(1)
				o(onupdate.callCount).equals(0)

				m.redraw()

				throttleMock.fire()

				o(onupdate.callCount).equals(1)
			})

			o("emits errors correctly", function() {
				errors = ["foo", "bar", "baz"]
				var counter = -1

				m.mount(root, createComponent({
					view: function() {
						var value = errors[counter++]
						if (value != null) throw value
						return null
					}
				}))

				m.redraw()
				throttleMock.fire()
				m.redraw()
				throttleMock.fire()
				m.redraw()
				throttleMock.fire()
			})
		})
	})
})


================================================
FILE: api/tests/test-router.js
================================================
"use strict"

// Low-priority TODO: remove the dependency on the renderer here.
var o = require("ospec")
var browserMock = require("../../test-utils/browserMock")
var throttleMocker = require("../../test-utils/throttleMock")

var m = require("../../render/hyperscript")
var coreRenderer = require("../../render/render")
var apiMountRedraw = require("../../api/mount-redraw")
var apiRouter = require("../../api/router")

o.spec("route", function() {
	// Note: the `n` parameter used in calls to this are generally found by
	// either trial-and-error or by studying the source. If tests are failing,
	// find the failing assertions, set `n` to about 10 on the preceding call to
	// `waitCycles`, then drop them down incrementally until it fails. The last
	// one to succeed is the one you want to keep. And just do that for each
	// failing assertion, and it'll eventually work.
	//
	// This is effectively what I did when designing this and hooking everything
	// up. (It would be so much easier to just be able to run the calls with a
	// different event loop and just turn it until I get what I want, but JS
	// lacks that functionality.)

	// Use precisely what `m.route` uses, for consistency and to ensure timings
	// are aligned.
	function waitCycles(n) {
		n = Math.max(n, 1)
		return new Promise(function(resolve) {
			return loop()
			function loop() {
				if (n === 0) resolve()
				else { n--; setTimeout(loop, 4) }
			}
		})
	}

	void [{protocol: "http:", hostname: "localhost"}, {protocol: "file:", hostname: "/"}, {protocol: "http:", hostname: "ööö"}].forEach(function(env) {
		void ["#", "?", "", "#!", "?!", "/foo", "/föö"].forEach(function(prefix) {
			o.spec("using prefix `" + prefix + "` starting on " + env.protocol + "//" + env.hostname, function() {
				var $window, root, mountRedraw, route, throttleMock
				var nextID = 0
				var currentTest = 0

				// Once done, a root should no longer be alive. This verifies
				// that, and it's a *very* subtle test bug that can lead to
				// some rather unusual consequences. If this fails, use
				// `waitCycles(n)` to avoid this.
				function lock(func) {
					var id = currentTest
					var start = Date.now()
					try {
						throw new Error()
					} catch (trace) {
						return function() {
							// This *will* cause a test failure.
							if (id != null && id !== currentTest) {
								id = undefined
								trace.message = "called " +
									(Date.now() - start) + "ms after test end"
								console.error(trace.stack)
								o("in test").equals("not in test")
							}
							return func.apply(this, arguments)
						}
					}
				}

				// In case it doesn't get reset
				var realError = console.error

				o.beforeEach(function() {
					currentTest = nextID++
					$window = browserMock(env)
					$window.setTimeout = setTimeout
					// $window.setImmediate = setImmediate
					throttleMock = throttleMocker()

					root = $window.document.body

					mountRedraw = apiMountRedraw(coreRenderer($window), throttleMock.schedule, console)
					route = apiRouter($window, mountRedraw)
					route.prefix = prefix
					console.error = function() {
						realError.call(this, new Error("Unexpected `console.error` call"))
						realError.apply(this, arguments)
					}
				})

				o.afterEach(function() {
					o(throttleMock.queueLength()).equals(0)
					currentTest = -1 // doesn't match any test
					console.error = realError
				})

				o("throws on invalid `root` DOM node", function() {
					var threw = false
					try {
						route(null, "/", {"/":{view: lock(function() {})}})
					} catch (e) {
						threw = true
					}
					o(threw).equals(true)
				})

				o("renders into `root`", function() {
					$window.location.href = prefix + "/"
					route(root, "/", {
						"/" : {
							view: lock(function() {
								return m("div")
							})
						}
					})

					o(root.firstChild.nodeName).equals("DIV")
				})

				o("resolves to route with escaped unicode", function() {
					$window.location.href = prefix + "/%C3%B6?%C3%B6=%C3%B6"
					route(root, "/ö", {
						"/ö" : {
							view: lock(function() {
								return m("div")
							})
						}
					})

					o(root.firstChild.nodeName).equals("DIV")
				})

				o("resolves to route with unicode", function() {
					$window.location.href = prefix + "/ö?ö=ö"
					route(root, "/ö", {
						"/ö" : {
							view: lock(function() {
								return JSON.stringify(route.param()) + " " +
									route.get()
							})
						}
					})

					o(root.firstChild.nodeValue).equals('{"ö":"ö"} /ö?ö=ö')
				})

				o("resolves to route with matching invalid escape", function() {
					$window.location.href = prefix + "/%C3%B6abc%def"
					route(root, "/öabc%def", {
						"/öabc%def" : {
							view: lock(function() {
								return route.get()
							})
						}
					})

					o(root.firstChild.nodeValue).equals("/öabc%def")
				})

				o("handles parameterized route", function() {
					$window.location.href = prefix + "/test/x"
					route(root, "/test/:a", {
						"/test/:a" : {
							view: lock(function(vnode) {
								return JSON.stringify(route.param()) + " " +
									JSON.stringify(vnode.attrs) + " " +
									route.get()
							})
						}
					})

					o(root.firstChild.nodeValue).equals(
						'{"a":"x"} {"a":"x"} /test/x'
					)
				})

				o("handles multi-parameterized route", function() {
					$window.location.href = prefix + "/test/x/y"
					route(root, "/test/:a/:b", {
						"/test/:a/:b" : {
							view: lock(function(vnode) {
								return JSON.stringify(route.param()) + " " +
									JSON.stringify(vnode.attrs) + " " +
									route.get()
							})
						}
					})

					o(root.firstChild.nodeValue).equals(
						'{"a":"x","b":"y"} {"a":"x","b":"y"} /test/x/y'
					)
				})

				o("handles rest parameterized route", function() {
					$window.location.href = prefix + "/test/x/y"
					route(root, "/test/:a...", {
						"/test/:a..." : {
							view: lock(function(vnode) {
								return JSON.stringify(route.param()) + " " +
									JSON.stringify(vnode.attrs) + " " +
									route.get()
							})
						}
					})

					o(root.firstChild.nodeValue).equals(
						'{"a":"x/y"} {"a":"x/y"} /test/x/y'
					)
				})

				o("keeps trailing / in rest parameterized route", function() {
					$window.location.href = prefix + "/test/d/"
					route(root, "/test/:a...", {
						"/test/:a..." : {
							view: lock(function(vnode) {
								return JSON.stringify(route.param()) + " " +
									JSON.stringify(vnode.attrs) + " " +
									route.get()
							})
						}
					})

					o(root.firstChild.nodeValue).equals(
						'{"a":"d/"} {"a":"d/"} /test/d/'
					)
				})

				o("remove trailing slash to match route if it is before rest operator match (...) ", function() {
					$window.location.href = prefix + "/test/d/"
					route(root, "/test/some/path", {
						"/test/:a" : {
							view: lock(function(vnode) {
								return JSON.stringify(route.param()) + " " +
									JSON.stringify(vnode.attrs) + " " +
									route.get()
							})
						},
						"/test/:a..." : {
							view: lock(function(vnode) {
								return JSON.stringify(route.param()) + " " +
									JSON.stringify(vnode.attrs) + " " +
									route.get()
							})
						},
					})

					o(root.firstChild.nodeValue).equals(
						'{"a":"d"} {"a":"d"} /test/d/'
					)
				})

				o("handles route with search", function() {
					$window.location.href = prefix + "/test?a=b&c=d"
					route(root, "/test", {
						"/test" : {
							view: lock(function(vnode) {
								return JSON.stringify(route.param()) + " " +
									JSON.stringify(vnode.attrs) + " " +
									route.get()
							})
						}
					})

					o(root.firstChild.nodeValue).equals(
						'{"a":"b","c":"d"} {"a":"b","c":"d"} /test?a=b&c=d'
					)
				})

				o("redirects to default route if no match", function() {
					$window.location.href = prefix + "/test"
					route(root, "/other", {
						"/other": {
							view: lock(function(vnode) {
								return JSON.stringify(route.param()) + " " +
									JSON.stringify(vnode.attrs) + " " +
									route.get()
							})
						}
					})

					return waitCycles(1).then(function() {
						o(root.firstChild.nodeValue).equals("{} {} /other")
					})
				})

				o("handles out of order routes", function() {
					$window.location.href = prefix + "/z/y/x"

					route(root, "/z/y/x", {
						"/z/y/x": {
							view: lock(function() { return "1" }),
						},
						"/:a...": {
							view: lock(function() { return "2" }),
						},
					})

					o(root.firstChild.nodeValue).equals("1")
				})

				o("handles reverse out of order routes", function() {
					$window.location.href = prefix + "/z/y/x"

					route(root, "/z/y/x", {
						"/:a...": {
							view: lock(function() { return "2" }),
						},
						"/z/y/x": {
							view: lock(function() { return "1" }),
						},
					})

					o(root.firstChild.nodeValue).equals("2")
				})

				o("resolves to route on fallback mode", function() {
					$window.location.href = "file://" + prefix + "/test"

					route(root, "/test", {
						"/test" : {
							view: lock(function(vnode) {
								return JSON.stringify(route.param()) + " " +
									JSON.stringify(vnode.attrs) + " " +
									route.get()
							})
						}
					})

					o(root.firstChild.nodeValue).equals("{} {} /test")
				})

				o("routed mount points only redraw asynchronously (POJO component)", function() {
					var view = o.spy()

					$window.location.href = prefix + "/"
					route(root, "/", {"/":{view:view}})

					o(view.callCount).equals(1)

					mountRedraw.redraw()

					o(view.callCount).equals(1)

					throttleMock.fire()

					o(view.callCount).equals(2)
				})

				o("routed mount points only redraw asynchronously (constructible component)", function() {
					var view = o.spy()

					var Cmp = lock(function(){})
					Cmp.prototype.view = lock(view)

					$window.location.href = prefix + "/"
					route(root, "/", {"/":Cmp})

					o(view.callCount).equals(1)

					mountRedraw.redraw()

					o(view.callCount).equals(1)

					throttleMock.fire()

					o(view.callCount).equals(2)
				})

				o("routed mount points only redraw asynchronously (closure component)", function() {
					var view = o.spy()

					function Cmp() {return {view: lock(view)}}

					$window.location.href = prefix + "/"
					route(root, "/", {"/":lock(Cmp)})

					o(view.callCount).equals(1)

					mountRedraw.redraw()

					o(view.callCount).equals(1)

					throttleMock.fire()

					o(view.callCount).equals(2)
				})

				o("subscribes correctly and removes when unmounted", function() {
					$window.location.href = prefix + "/"

					route(root, "/", {
						"/" : {
							view: lock(function() {
								return m("div")
							})
						}
					})

					o(root.firstChild.nodeName).equals("DIV")

					mountRedraw.mount(root)

					o(root.childNodes.length).equals(0)
				})

				o("default route doesn't break back button", function() {
					$window.location.href = "http://old.com"
					$window.location.href = "http://new.com"

					route(root, "/a", {
						"/a" : {
							view: lock(function() {
								return m("div")
							})
						}
					})

					return waitCycles(1).then(function() {
						o(root.firstChild.nodeName).equals("DIV")

						o(route.get()).equals("/a")

						$window.history.back()

						o($window.location.pathname).equals("/")
						o($window.location.hostname).equals("old.com")
					})
				})

				o("default route does not inherit params", function() {
					$window.location.href = "/invalid?foo=bar"
					route(root, "/a", {
						"/a" : {
							oninit: lock(function(vnode) {
								o(vnode.attrs.foo).equals(undefined)
							}),
							view: lock(function() {
								return m("div")
							})
						}
					})

					return waitCycles(1)
				})

				o("redraws when render function is executed", function() {
					var onupdate = o.spy()
					var oninit = o.spy()

					$window.location.href = prefix + "/"
					route(root, "/", {
						"/" : {
							view: lock(function() {
								return m("div", {
									oninit: oninit,
									onupdate: onupdate
								})
							})
						}
					})

					o(oninit.callCount).equals(1)

					mountRedraw.redraw()
					throttleMock.fire()

					o(onupdate.callCount).equals(1)
				})

				o("redraws on events", function() {
					var onupdate = o.spy()
					var oninit = o.spy()
					var onclick = o.spy()
					var e = $window.document.createEvent("MouseEvents")

					e.initEvent("click", true, true)

					$window.location.href = prefix + "/"
					route(root, "/", {
						"/" : {
							view: lock(function() {
								return m("div", {
									oninit: oninit,
									onupdate: onupdate,
									onclick: onclick,
								})
							})
						}
					})

					root.firstChild.dispatchEvent(e)

					o(oninit.callCount).equals(1)

					o(onclick.callCount).equals(1)
					o(onclick.this).equals(root.firstChild)
					o(onclick.args[0].type).equals("click")
					o(onclick.args[0].target).equals(root.firstChild)


					throttleMock.fire()
					o(onupdate.callCount).equals(1)
				})

				o("event handlers can skip redraw", function() {
					var onupdate = o.spy()
					var oninit = o.spy()
					var e = $window.document.createEvent("MouseEvents")

					e.initEvent("click", true, true)

					$window.location.href = prefix + "/"
					route(root, "/", {
						"/" : {
							view: lock(function() {
								return m("div", {
									oninit: oninit,
									onupdate: onupdate,
									onclick: lock(function(e) {
										e.redraw = false
									}),
								})
							})
						}
					})

					o(oninit.callCount).equals(1)

					root.firstChild.dispatchEvent(e)
					throttleMock.fire()

					// Wrapped to ensure no redraw fired
					return waitCycles(1).then(function() {
						o(onupdate.callCount).equals(0)
					})
				})

				o("changes location on route.Link", function() {
					var e = $window.document.createEvent("MouseEvents")

					e.initEvent("click", true, true)
					e.button = 0

					$window.location.href = prefix + "/"
					route(root, "/", {
						"/" : {
							view: lock(function() {
								return m(route.Link, {href: "/test"})
							})
						},
						"/test" : {
							view : lock(function() {
								return m("div")
							})
						}
					})

					var slash = prefix[0] === "/" ? "" : "/"

					o($window.location.href).equals(env.protocol + "//" + (env.hostname === "/" ? "" : env.hostname) + slash + (prefix ? prefix + "/" : ""))

					root.firstChild.dispatchEvent(e)
					throttleMock.fire()
					o($window.location.href).equals(env.protocol + "//" + (env.hostname === "/" ? "" : env.hostname) + slash + (prefix ? prefix + "/" : "") + "test")
				})

				o("passes options on route.Link", function() {
					var opts = {}
					var e = $window.document.createEvent("MouseEvents")

					e.initEvent("click", true, true)
					e.button = 0
					$window.location.href = prefix + "/"

					route(root, "/", {
						"/" : {
							view: lock(function() {
								return m(route.Link, {
									href: "/test",
									options: opts,
								})
							})
						},
						"/test" : {
							view : lock(function() {
								return m("div")
							})
						}
					})
					route.set = o.spy(route.set)

					root.firstChild.dispatchEvent(e)

					o(route.set.callCount).equals(1)
					o(route.set.args[2]).equals(opts)
				})

				o("passes params on route.Link", function() {
					var e = $window.document.createEvent("MouseEvents")

					e.initEvent("click", true, true)
					e.button = 0
					$window.location.href = prefix + "/"

					route(root, "/", {
						"/" : {
							view: lock(function() {
								return m(route.Link, {
									href: "/test",
									params: {key: "value"},
								})
							})
						},
						"/test" : {
							view : lock(function() {
								return m("div")
							})
						}
					})
					route.set = o.spy(route.set)

					root.firstChild.dispatchEvent(e)

					o(route.set.callCount).equals(1)
					o(route.set.args[0]).equals("/test?key=value")
				})

				o("route.Link can render without routes or dom access", function() {
					$window = browserMock(env)
					var render = coreRenderer($window)
					route = apiRouter(null, null)
					route.prefix = prefix
					root = $window.document.body

					render(root, m(route.Link, {href: "/test", foo: "bar"}, "text"))

					o(root.childNodes.length).equals(1)
					o(root.firstChild.nodeName).equals("A")
					o(root.firstChild.href).equals(prefix + "/test")
					o(root.firstChild.hasAttribute("aria-disabled")).equals(false)
					o(root.firstChild.hasAttribute("disabled")).equals(false)
					o(root.firstChild.attributes["foo"].value).equals("bar")
					o(root.firstChild.childNodes.length).equals(1)
					o(root.firstChild.firstChild.nodeName).equals("#text")
					o(root.firstChild.firstChild.nodeValue).equals("text")
				})

				o("route.Link keeps magic attributes from being double-called", function() {
					$window = browserMock(env)
					var render = coreRenderer($window)
					route = apiRouter(null, null)
					route.prefix = prefix
					root = $window.document.body

					var oninit = o.spy()
					var oncreate = o.spy()
					var onbeforeupdate = o.spy()
					var onupdate = o.spy()
					var onbeforeremove = o.spy()
					var onremove = o.spy()

					render(root, m(route.Link, {
						href: "/test",
						oninit: oninit,
						oncreate: oncreate,
						onbeforeupdate: onbeforeupdate,
						onupdate: onupdate,
						onbeforeremove: onbeforeremove,
						onremove: onremove,
					}, "text"))

					o(oninit.callCount).equals(1)
					o(oncreate.callCount).equals(1)
					o(onbeforeupdate.callCount).equals(0)
					o(onupdate.callCount).equals(0)
					o(onbeforeremove.callCount).equals(0)
					o(onremove.callCount).equals(0)

					render(root, m(route.Link, {
						href: "/test",
						oninit: oninit,
						oncreate: oncreate,
						onbeforeupdate: onbeforeupdate,
						onupdate: onupdate,
						onbeforeremove: onbeforeremove,
						onremove: onremove,
					}, "text"))

					o(oninit.callCount).equals(1)
					o(oncreate.callCount).equals(1)
					o(onbeforeupdate.callCount).equals(1)
					o(onupdate.callCount).equals(1)
					o(onbeforeremove.callCount).equals(0)
					o(onremove.callCount).equals(0)

					render(root, [])

					o(oninit.callCount).equals(1)
					o(oncreate.callCount).equals(1)
					o(onbeforeupdate.callCount).equals(1)
					o(onupdate.callCount).equals(1)
					o(onbeforeremove.callCount).equals(1)
					o(onremove.callCount).equals(1)
				})

				o("route.Link can render other tag without routes or dom access", function() {
					$window = browserMock(env)
					var render = coreRenderer($window)
					route = apiRouter(null, null)
					route.prefix = prefix
					root = $window.document.body

					render(root, m(route.Link, {selector: "button", href: "/test", foo: "bar"}, "text"))

					o(root.childNodes.length).equals(1)
					o(root.firstChild.nodeName).equals("BUTTON")
					o(root.firstChild.attributes["href"].value).equals(prefix + "/test")
					o(root.firstChild.hasAttribute("aria-disabled")).equals(false)
					o(root.firstChild.hasAttribute("disabled")).equals(false)
					o(root.firstChild.attributes["foo"].value).equals("bar")
					o(root.firstChild.childNodes.length).equals(1)
					o(root.firstChild.firstChild.nodeName).equals("#text")
					o(root.firstChild.firstChild.nodeValue).equals("text")
				})

				o("route.Link can render other selector without routes or dom access", function() {
					$window = browserMock(env)
					var render = coreRenderer($window)
					route = apiRouter(null, null)
					route.prefix = prefix
					root = $window.document.body

					render(root, m(route.Link, {selector: "button[href=/test]", foo: "bar"}, "text"))

					o(root.childNodes.length).equals(1)
					o(root.firstChild.nodeName).equals("BUTTON")
					o(root.firstChild.attributes["href"].value).equals(prefix + "/test")
					o(root.firstChild.hasAttribute("aria-disabled")).equals(false)
					o(root.firstChild.hasAttribute("disabled")).equals(false)
					o(root.firstChild.attributes["foo"].value).equals("bar")
					o(root.firstChild.childNodes.length).equals(1)
					o(root.firstChild.firstChild.nodeName).equals("#text")
					o(root.firstChild.firstChild.nodeValue).equals("text")
				})

				o("route.Link can render not disabled", function() {
					$window = browserMock(env)
					var render = coreRenderer($window)
					route = apiRouter(null, null)
					route.prefix = prefix
					root = $window.document.body

					render(root, m(route.Link, {href: "/test", disabled: false, foo: "bar"}, "text"))

					o(root.childNodes.length).equals(1)
					o(root.firstChild.nodeName).equals("A")
					o(root.firstChild.href).equals(prefix + "/test")
					o(root.firstChild.hasAttribute("aria-disabled")).equals(false)
					o(root.firstChild.hasAttribute("disabled")).equals(false)
					o(root.firstChild.attributes["foo"].value).equals("bar")
					o(root.firstChild.childNodes.length).equals(1)
					o(root.firstChild.firstChild.nodeName).equals("#text")
					o(root.firstChild.firstChild.nodeValue).equals("text")
				})

				o("route.Link can render falsy disabled", function() {
					$window = browserMock(env)
					var render = coreRenderer($window)
					route = apiRouter(null, null)
					route.prefix = prefix
					root = $window.document.body

					render(root, m(route.Link, {href: "/test", disabled: 0, foo: "bar"}, "text"))

					o(root.childNodes.length).equals(1)
					o(root.firstChild.nodeName).equals("A")
					o(root.firstChild.href).equals(prefix + "/test")
					o(root.firstChild.hasAttribute("aria-disabled")).equals(false)
					o(root.firstChild.hasAttribute("disabled")).equals(false)
					o(root.firstChild.attributes["foo"].value).equals("bar")
					o(root.firstChild.childNodes.length).equals(1)
					o(root.firstChild.firstChild.nodeName).equals("#text")
					o(root.firstChild.firstChild.nodeValue).equals("text")
				})

				o("route.Link can render disabled", function() {
					$window = browserMock(env)
					var render = coreRenderer($window)
					route = apiRouter(null, null)
					route.prefix = prefix
					root = $window.document.body

					render(root, m(route.Link, {href: "/test", disabled: true, foo: "bar"}, "text"))

					o(root.childNodes.length).equals(1)
					o(root.firstChild.nodeName).equals("A")
					o(root.firstChild.href).equals("")
					o(root.firstChild.attributes["aria-disabled"].value).equals("true")
					o(root.firstChild.attributes["foo"].value).equals("bar")
					o(root.firstChild.attributes["disabled"].value).equals("")
					o(root.firstChild.childNodes.length).equals(1)
					o(root.firstChild.firstChild.nodeName).equals("#text")
					o(root.firstChild.firstChild.nodeValue).equals("text")
				})

				o("route.Link can render truthy disabled", function() {
					$window = browserMock(env)
					var render = coreRenderer($window)
					route = apiRouter(null, null)
					route.prefix = prefix
					root = $window.document.body

					render(root, m(route.Link, {href: "/test", disabled: 1, foo: "bar"}, "text"))

					o(root.childNodes.length).equals(1)
					o(root.firstChild.nodeName).equals("A")
					o(root.firstChild.href).equals("")
					o(root.firstChild.attributes["aria-disabled"].value).equals("true")
					o(root.firstChild.attributes["foo"].value).equals("bar")
					o(root.firstChild.attributes["disabled"].value).equals("")
					o(root.firstChild.childNodes.length).equals(1)
					o(root.firstChild.firstChild.nodeName).equals("#text")
					o(root.firstChild.firstChild.nodeValue).equals("text")
				})

				o("route.Link doesn't redraw on wrong button", function() {
					var e = $window.document.createEvent("MouseEvents")

					e.initEvent("click", true, true)
					e.button = 10

					$window.location.href = prefix + "/"
					route(root, "/", {
						"/" : {
							view: lock(function() {
								return m(route.Link, {href: "/test"})
							})
						},
						"/test" : {
							view : lock(function() {
								return m("div")
							})
						}
					})

					var slash = prefix[0] === "/" ? "" : "/"

					o($window.location.href).equals(env.protocol + "//" + (env.hostname === "/" ? "" : env.hostname) + slash + (prefix ? prefix + "/" : ""))

					root.firstChild.dispatchEvent(e)
					throttleMock.fire()
					o($window.location.href).equals(env.protocol + "//" + (env.hostname === "/" ? "" : env.hostname) + slash + (prefix ? prefix + "/" : ""))
				})

				o("route.Link doesn't redraw on preventDefault", function() {
					var e = $window.document.createEvent("MouseEvents")

					e.initEvent("click", true, true)
					e.button = 0

					$window.location.href = prefix + "/"
					route(root, "/", {
						"/" : {
							view: lock(function() {
								return m(route.Link, {
									href: "/test",
									onclick: function(e) {
										e.preventDefault()
									}
								})
							})
						},
						"/test" : {
							view : lock(function() {
								return m("div")
							})
						}
					})

					var slash = prefix[0] === "/" ? "" : "/"

					o($window.location.href).equals(env.protocol + "//" + (env.hostname === "/" ? "" : env.hostname) + slash + (prefix ? prefix + "/" : ""))

					root.firstChild.dispatchEvent(e)
					throttleMock.fire()
					o($window.location.href).equals(env.protocol + "//" + (env.hostname === "/" ? "" : env.hostname) + slash + (prefix ? prefix + "/" : ""))
				})

				o("route.Link doesn't redraw on preventDefault in handleEvent", function() {
					var e = $window.document.createEvent("MouseEvents")

					e.initEvent("click", true, true)
					e.button = 0

					$window.location.href = prefix + "/"
					route(root, "/", {
						"/" : {
							view: lock(function() {
								return m(route.Link, {
									href: "/test",
									onclick: {
										handleEvent: function(e) {
											e.preventDefault()
										}
									}
								})
							})
						},
						"/test" : {
							view : lock(function() {
								return m("div")
							})
						}
					})

					var slash = prefix[0] === "/" ? "" : "/"

					o($window.location.href).equals(env.protocol + "//" + (env.hostname === "/" ? "" : env.hostname) + slash + (prefix ? prefix + "/" : ""))

					root.firstChild.dispatchEvent(e)
					throttleMock.fire()
					o($window.location.href).equals(env.protocol + "//" + (env.hostname === "/" ? "" : env.hostname) + slash + (prefix ? prefix + "/" : ""))
				})

				o("route.Link doesn't redraw on return false", function() {
					var e = $window.document.createEvent("MouseEvents")

					e.initEvent("click", true, true)
					e.button = 0

					$window.location.href = prefix + "/"
					route(root, "/", {
						"/" : {
							view: lock(function() {
								return m(route.Link, {
									href: "/test",
									onclick: function() {
										return false
									}
								})
							})
						},
						"/test" : {
							view : lock(function() {
								return m("div")
							})
						}
					})

					var slash = prefix[0] === "/" ? "" : "/"

					o($window.location.href).equals(env.protocol + "//" + (env.hostname === "/" ? "" : env.hostname) + slash + (prefix ? prefix + "/" : ""))

					root.firstChild.dispatchEvent(e)
					throttleMock.fire()
					o($window.location.href).equals(env.protocol + "//" + (env.hostname === "/" ? "" : env.hostname) + slash + (prefix ? prefix + "/" : ""))
				})

				o("accepts RouteResolver with onmatch that returns Component", function() {
					var matchCount = 0
					var renderCount = 0
					var Component = {
						view: lock(function() {
							return m("span")
						})
					}

					var resolver = {
						onmatch: lock(function(args, requestedPath, route) {
							matchCount++

							o(args.id).equals("abc")
							o(requestedPath).equals("/abc")
							o(route).equals("/:id")
							o(this).equals(resolver)
							return Component
						}),
						render: lock(function(vnode) {
							renderCount++

							o(vnode.attrs.id).equals("abc")
							o(this).equals(resolver)

							return vnode
						}),
					}

					$window.location.href = prefix + "/abc"
					route(root, "/abc", {
						"/:id" : resolver
					})

					return waitCycles(1).then(function() {
						o(matchCount).equals(1)
						o(renderCount).equals(1)
						o(root.firstChild.nodeName).equals("SPAN")
					})
				})

				o("accepts RouteResolver with onmatch that returns route.SKIP", function() {
					var match1Count = 0
					var match2Count = 0
					var render1 = o.spy()
					var render2Count = 0
					var Component = {
						view: lock(function() {
							return m("span")
						})
					}

					var resolver1 = {
						onmatch: lock(function(args, requestedPath, key) {
							match1Count++

							o(args.id1).equals("abc")
							o(requestedPath).equals("/abc")
							o(key).equals("/:id1")
							o(this).equals(resolver1)
							return route.SKIP
						}),
						render: lock(render1),
					}

					var resolver2 = {
						onmatch: function(args, requestedPath, key) {
							match2Count++

							o(args.id2).equals("abc")
							o(requestedPath).equals("/abc")
							o(key).equals("/:id2")
							o(this).equals(resolver2)
							return Component
						},
						render: function(vnode) {
							render2Count++

							o(vnode.attrs.id2).equals("abc")
							o(this).equals(resolver2)
							o(render1.callCount).equals(0)

							return vnode
						},
					}

					$window.location.href = prefix + "/abc"
					route(root, "/abc", {
						"/:id1" : resolver1,
						"/:id2" : resolver2
					})

					return waitCycles(4).then(function() {
						o(match1Count).equals(1)
						o(match2Count).equals(1)
						o(render2Count).equals(1)
						o(render1.callCount).equals(0)
						o(root.firstChild.nodeName).equals("SPAN")
					})
				})

				o("accepts RouteResolver with onmatch that returns Promise<Component>", function() {
					var matchCount = 0
					var renderCount = 0
					var Component = {
						view: lock(function() {
							return m("span")
						})
					}

					var resolver = {
						onmatch: lock(function(args, requestedPath, route) {
							matchCount++

							o(args.id).equals("abc")
							o(requestedPath).equals("/abc")
							o(route).equals("/:id")
							o(this).equals(resolver)
							return Promise.resolve(Component)
						}),
						render: lock(function(vnode) {
							renderCount++

							o(vnode.attrs.id).equals("abc")
							o(this).equals(resolver)

							return vnode
						}),
					}

					$window.location.href = prefix + "/abc"
					route(root, "/abc", {
						"/:id" : resolver
					})

					return waitCycles(10).then(function() {
						o(matchCount).equals(1)
						o(renderCount).equals(1)
						o(root.firstChild.nodeName).equals("SPAN")
					})
				})

				o("accepts RouteResolver with onmatch that returns Promise<undefined>", function() {
					var matchCount = 0
					var renderCount = 0

					var resolver = {
						onmatch: lock(function(args, requestedPath, route) {
							matchCount++

							o(args.id).equals("abc")
							o(requestedPath).equals("/abc")
							o(route).equals("/:id")
							o(this).equals(resolver)
							return Promise.resolve()
						}),
						render: lock(function(vnode) {
							renderCount++

							o(vnode.attrs.id).equals("abc")
							o(this).equals(resolver)

							return vnode
						}),
					}

					$window.location.href = prefix + "/abc"
					route(root, "/abc", {
						"/:id" : resolver
					})

					return waitCycles(2).then(function() {
						o(matchCount).equals(1)
						o(renderCount).equals(1)
						o(root.firstChild.nodeName).equals("DIV")
					})
				})

				o("accepts RouteResolver with onmatch that returns Promise<any>", function() {
					var matchCount = 0
					var renderCount = 0

					var resolver = {
						onmatch: lock(function(args, requestedPath, route) {
							matchCount++

							o(args.id).equals("abc")
							o(requestedPath).equals("/abc")
							o(route).equals("/:id")
							o(this).equals(resolver)
							return Promise.resolve([])
						}),
						render: lock(function(vnode) {
							renderCount++

							o(vnode.attrs.id).equals("abc")
							o(this).equals(resolver)

							return vnode
						}),
					}

					$window.location.href = prefix + "/abc"
					route(root, "/abc", {
						"/:id" : resolver
					})

					return waitCycles(2).then(function() {
						o(matchCount).equals(1)
						o(renderCount).equals(1)
						o(root.firstChild.nodeName).equals("DIV")
					})
				})

				o("accepts RouteResolver with onmatch that returns rejected Promise", function() {
					var matchCount = 0
					var renderCount = 0
					var spy = o.spy()
					var error = new Error("error")
					var errorSpy = console.error = o.spy()

					var resolver = {
						onmatch: lock(function() {
							matchCount++
							return Promise.reject(error)
						}),
						render: lock(function(vnode) {
							renderCount++
							return vnode
						}),
					}

					$window.location.href = prefix + "/test/1"
					route(root, "/default", {
						"/default" : {view: spy},
						"/test/:id" : resolver
					})

					return waitCycles(3).then(function() {
						o(matchCount).equals(1)
						o(renderCount).equals(0)
						o(spy.callCount).equals(1)
						o(errorSpy.callCount).equals(1)
						o(errorSpy.args[0]).equals(error)
					})
				})

				o("accepts RouteResolver without `render` method as payload", function() {
					var matchCount = 0
					var Component = {
						view: lock(function() {
							return m("div")
						})
					}

					$window.location.href = prefix + "/abc"
					route(root, "/abc", {
						"/:id" : {
							onmatch: lock(function(args, requestedPath, route) {
								matchCount++

								o(args.id).equals("abc")
								o(requestedPath).equals("/abc")
								o(route).equals("/:id")

								return Component
							}),
						},
					})

					return waitCycles(2).then(function() {
						o(matchCount).equals(1)
						o(root.firstChild.nodeName).equals("DIV")
					})
				})

				o("changing `key` param resets the component", function(){
					var oninit = o.spy()
					var Component = {
						oninit: oninit,
						view: lock(function() {
							return m("div")
						})
					}
					$window.location.href = prefix + "/abc"
					route(root, "/abc", {
						"/:key": Component,
					})
					return waitCycles(1).then(function() {
						o(oninit.callCount).equals(1)
						route.set("/def")
						return waitCycles(1).then(function() {
							throttleMock.fire()
							o(oninit.callCount).equals(2)
						})
					})
				})

				o("accepts RouteResolver without `onmatch` method as payload", function() {
					var renderCount = 0
					var Component = {
						view: lock(function() {
							return m("div")
						})
					}

					$window.location.href = prefix + "/abc"
					route(root, "/abc", {
						"/:id" : {
							render: lock(function(vnode) {
								renderCount++

								o(vnode.attrs.id).equals("abc")

								return m(Component)
							}),
						},
					})

					o(root.firstChild.nodeName).equals("DIV")
					o(renderCount).equals(1)
				})

				o("RouteResolver `render` does not have component semantics", function() {
					$window.location.href = prefix + "/a"
					route(root, "/a", {
						"/a" : {
							render: lock(function() {
								return m("div", m("p"))
							}),
						},
						"/b" : {
							render: lock(function() {
								return m("div", m("a"))
							}),
						},
					})

					var dom = root.firstChild
					var child = dom.firstChild

					o(root.firstChild.nodeName).equals("DIV")

					route.set("/b")

					return waitCycles(1).then(function() {
						throttleMock.fire()

						o(root.firstChild).equals(dom)
						o(root.firstChild.firstChild).notEquals(child)
					})
				})

				o("calls onmatch and view correct number of times", function() {
					var matchCount = 0
					var renderCount = 0
					var Component = {
						view: lock(function() {
							return m("div")
						})
					}

					$window.location.href = prefix + "/"
					route(root, "/", {
						"/" : {
							onmatch: lock(function() {
								matchCount++
								return Component
							}),
							render: lock(function(vnode) {
								renderCount++
								return vnode
							}),
						},
					})

					return waitCycles(1).then(function() {
						o(matchCount).equals(1)
						o(renderCount).equals(1)

						mountRedraw.redraw()
						throttleMock.fire()

						o(matchCount).equals(1)
						o(renderCount).equals(2)
					})
				})

				o("calls onmatch and view correct number of times when not onmatch returns undefined", function() {
					var matchCount = 0
					var renderCount = 0
					var Component = {
						view: lock(function() {
							return m("div")
						})
					}

					$window.location.href = prefix + "/"
					route(root, "/", {
						"/" : {
							onmatch: lock(function() {
								matchCount++
							}),
							render: lock(function() {
								renderCount++
								return m(Component)
							}),
						},
					})

					return waitCycles(2).then(function() {
						o(matchCount).equals(1)
						o(renderCount).equals(1)

						mountRedraw.redraw()
						throttleMock.fire()

						o(matchCount).equals(1)
						o(renderCount).equals(2)
					})
				})

				o("onmatch can redirect to another route", function() {
					var redirected = false
					var render = o.spy()

					$window.location.href = prefix + "/a"
					route(root, "/a", {
						"/a" : {
							onmatch: lock(function() {
								route.set("/b")
							}),
							render: lock(render)
						},
						"/b" : {
							view: lock(function() {
								redirected = true
							})
						}
					})

					return waitCycles(2).then(function() {
						o(render.callCount).equals(0)
						o(redirected).equals(true)
					})
				})

				o("onmatch can redirect to another route that has RouteResolver with only onmatch", function() {
					var redirected = false
					var render = o.spy()
					var view = o.spy(function() {return m("div")})

					$window.location.href = prefix + "/a"
					route(root, "/a", {
						"/a" : {
							onmatch: lock(function() {
								route.set("/b", {}, {state: {a: 5}})
							}),
							render: lock(render)
						},
						"/b" : {
							onmatch: lock(function() {
								redirected = true
								return {view: lock(view)}
							})
						}
					})

					return waitCycles(3).then(function() {
						o(render.callCount).equals(0)
						o(redirected).equals(true)
						o(view.callCount).equals(1)
						o(root.childNodes.length).equals(1)
						o(root.firstChild.nodeName).equals("DIV")
						o($window.history.state).deepEquals({a: 5})
					})
				})

				o("onmatch can redirect to another route that has RouteResolver with only render", function() {
					var redirected = false
					var render = o.spy()

					$window.location.href = prefix + "/a"
					route(root, "/a", {
						"/a" : {
							onmatch: lock(function() {
								route.set("/b")
							}),
							render: lock(render)
						},
						"/b" : {
							render: lock(function(){
								redirected = true
							})
						}
					})

					return waitCycles(2).then(function() {
						o(render.callCount).equals(0)
						o(redirected).equals(true)
					})
				})

				o("onmatch can redirect to another route that has RouteResolver whose onmatch resolves asynchronously", function() {
					var redirected = false
					var render = o.spy()
					var view = o.spy()

					$window.location.href = prefix + "/a"
					route(root, "/a", {
						"/a" : {
							onmatch: lock(function() {
								route.set("/b")
							}),
							render: lock(render)
						},
						"/b" : {
							onmatch: lock(function() {
								redirected = true
								return waitCycles(1).then(function(){
									return {view: view}
								})
							})
						}
					})

					return waitCycles(6).then(function() {
						o(render.callCount).equals(0)
						o(redirected).equals(true)
						o(view.callCount).equals(1)
					})
				})

				o("onmatch can redirect to another route asynchronously", function() {
					var redirected = false
					var render = o.spy()
					var view = o.spy()

					$window.location.href = prefix + "/a"
					route(root, "/a", {
						"/a" : {
							onmatch: lock(function() {
								waitCycles(1).then(function() {route.set("/b")})
								return new Promise(function() {})
							}),
							render: lock(render)
						},
						"/b" : {
							onmatch: lock(function() {
								redirected = true
								return {view: lock(view)}
							})
						}
					})

					return waitCycles(5).then(function() {
						o(render.callCount).equals(0)
						o(redirected).equals(true)
						o(view.callCount).equals(1)
					})
				})

				o("onmatch can redirect with window.history.back()", function() {

					var render = o.spy()
					var component = {view: o.spy()}

					$window.location.href = prefix + "/a"
					route(root, "/a", {
						"/a" : {
							onmatch: lock(function() {
								return component
							}),
							render: lock(function(vnode) {
								return vnode
							})
						},
						"/b" : {
							onmatch: lock(function() {
								$window.history.back()
								return new Promise(function() {})
							}),
							render: lock(render)
						}
					})

					return waitCycles(2).then(function() {
						throttleMock.fire()

						route.set("/b")
						o(render.callCount).equals(0)
						o(component.view.callCount).equals(1)

						return waitCycles(4).then(function() {
							throttleMock.fire()

							o(render.callCount).equals(0)
							o(component.view.callCount).equals(2)
						})
					})
				})

				o("onmatch can redirect to a non-existent route that defaults to a RouteResolver with onmatch", function() {
					var redirected = false
					var render = o.spy()

					$window.location.href = prefix + "/a"
					route(root, "/b", {
						"/a" : {
							onmatch: lock(function() {
								route.set("/c")
							}),
							render: lock(render)
						},
						"/b" : {
							onmatch: lock(function(){
								redirected = true
								return {view: lock(function() {})}
							})
						}
					})

					return waitCycles(3).then(function() {
						o(render.callCount).equals(0)
						o(redirected).equals(true)
					})
				})

				o("onmatch can redirect to a non-existent route that defaults to a RouteResolver with render", function() {
					var redirected = false
					var render = o.spy()

					$window.location.href = prefix + "/a"
					route(root, "/b", {
						"/a" : {
							onmatch: lock(function() {
								route.set("/c")
							}),
							render: lock(render)
						},
						"/b" : {
							render: lock(function(){
								redirected = true
							})
						}
					})

					return waitCycles(3).then(function() {
						o(render.callCount).equals(0)
						o(redirected).equals(true)
					})
				})

				o("onmatch can redirect to a non-existent route that defaults to a component", function() {
					var redirected = false
					var render = o.spy()

					$window.location.href = prefix + "/a"
					route(root, "/b", {
						"/a" : {
							onmatch: lock(function() {
								route.set("/c")
							}),
							render: lock(render)
						},
						"/b" : {
							view: lock(function(){
								redirected = true
							})
						}
					})

					return waitCycles(3).then(function() {
						o(render.callCount).equals(0)
						o(redirected).equals(true)
					})
				})

				o("the previous view redraws while onmatch resolution is pending (#1268)", function() {
					var view = o.spy()
					var onmatch = o.spy(function() {
						return new Promise(function() {})
					})

					$window.location.href = prefix + "/a"
					route(root, "/", {
						"/a": {view: lock(view)},
						"/b": {onmatch: lock(onmatch)},
						"/": {view: lock(function() {})}
					})

					o(view.callCount).equals(1)
					o(onmatch.callCount).equals(0)

					route.set("/b")

					return waitCycles(1).then(function() {
						o(view.callCount).equals(1)
						o(onmatch.callCount).equals(1)

						mountRedraw.redraw()
						throttleMock.fire()

						o(view.callCount).equals(2)
						o(onmatch.callCount).equals(1)
					})
				})

				o("when two async routes are racing, the last one set cancels the finalization of the first", function(done) {
					var renderA = o.spy()
					var renderB = o.spy()
					var onmatchA = o.spy(function(){
						return waitCycles(3)
					})

					$window.location.href = prefix + "/a"
					route(root, "/a", {
						"/a": {
							onmatch: lock(onmatchA),
							render: lock(renderA)
						},
						"/b": {
							onmatch: lock(function(){
								var p = new Promise(function(fulfill) {
									o(onmatchA.callCount).equals(1)
									o(renderA.callCount).equals(0)
									o(renderB.callCount).equals(0)

									waitCycles(3).then(function(){
										o(onmatchA.callCount).equals(1)
										o(renderA.callCount).equals(0)
										o(renderB.callCount).equals(0)

										fulfill()
										return p
									}).then(function(){
										return waitCycles(1)
									}).then(function(){
										o(onmatchA.callCount).equals(1)
										o(renderA.callCount).equals(0)
										o(renderB.callCount).equals(1)
									}).then(done, done)
								})
								return p
							}),
							render: lock(renderB)
						}
					})

					waitCycles(1).then(lock(function() {
						o(onmatchA.callCount).equals(1)
						o(renderA.callCount).equals(0)
						o(renderB.callCount).equals(0)
						route.set("/b")
						o(onmatchA.callCount).equals(1)
						o(renderA.callCount).equals(0)
						o(renderB.callCount).equals(0)
					}))
				})

				o("m.route.set(m.route.get()) re-runs the resolution logic (#1180)", function(){
					var onmatch = o.spy()
					var render = o.spy(function() {return m("div")})

					$window.location.href = prefix + "/"
					route(root, "/", {
						"/": {
							onmatch: lock(onmatch),
							render: lock(render)
						}
					})

					return waitCycles(1).then(function() {
						throttleMock.fire()

						o(onmatch.callCount).equals(1)
						o(render.callCount).equals(1)

						route.set(route.get())

						return waitCycles(2).then(function() {
							throttleMock.fire()

							o(onmatch.callCount).equals(2)
							o(render.callCount).equals(2)
						})
					})
				})

				o("m.route.get() returns the last fully resolved route (#1276)", function(){
					$window.location.href = prefix + "/"

					route(root, "/", {
						"/": {view: lock(function() {})},
						"/2": {
							onmatch: lock(function() {
								return new Promise(function() {})
							})
						}
					})


					o(route.get()).equals("/")

					route.set("/2")

					return waitCycles(1).then(function() {
						o(route.get()).equals("/")
					})
				})

				o("routing with RouteResolver works more than once", function() {
					$window.location.href = prefix + "/a"
					route(root, "/a", {
						"/a": {
							render: lock(function() {
								return m("a", "a")
							})
						},
						"/b": {
							render: lock(function() {
								return m("b", "b")
							})
						}
					})

					route.set("/b")

					return waitCycles(1).then(function() {
						throttleMock.fire()

						o(root.firstChild.nodeName).equals("B")

						route.set("/a")

						return waitCycles(1).then(function() {
							throttleMock.fire()

							o(root.firstChild.nodeName).equals("A")
						})
					})
				})

				o("calling route.set invalidates pending onmatch resolution", function() {
					var rendered = false
					var resolved
					$window.location.href = prefix + "/a"
					route(root, "/a", {
						"/a": {
							onmatch: lock(function() {
								return waitCycles(2).then(function() {
									return {view: lock(function() {rendered = true})}
								})
							}),
							render: lock(function() {
								rendered = true
								resolved = "a"
							})
						},
						"/b": {
							view: lock(function() {
								resolved = "b"
							})
						}
					})

					route.set("/b")

					return waitCycles(1).then(function() {
						o(rendered).equals(false)
						o(resolved).equals("b")

						return waitCycles(1).then(function() {
							o(rendered).equals(false)
							o(resolved).equals("b")
						})
					})
				})

				o("route changes activate onbeforeremove", function() {
					var spy = o.spy()

					$window.location.href = prefix + "/a"
					route(root, "/a", {
						"/a": {
							onbeforeremove: lock(spy),
							view: lock(function() {})
						},
						"/b": {
							view: lock(function() {})
						}
					})

					route.set("/b")

					// setting the route is asynchronous
					return waitCycles(1).then(function() {
						throttleMock.fire()
						o(spy.callCount).equals(1)
					})
				})

				o("asynchronous route.set in onmatch works", function() {
					var rendered = false, resolved
					route(root, "/a", {
						"/a": {
							onmatch: lock(function() {
								return Promise.resolve().then(lock(function() {
									route.set("/b")
								}))
							}),
							render: lock(function() {
								rendered = true
								resolved = "a"
							})
						},
						"/b": {
							view: lock(function() {
								resolved = "b"
							})
						},
					})

					// tick for popstate for /a
					// tick for onmatch
					// tick for promise in onmatch
					// tick for onpopstate for /b
					return waitCycles(4).then(function() {
						o(rendered).equals(false)
						o(resolved).equals("b")
					})
				})

				o("throttles", function() {
					var i = 0
					$window.location.href = prefix + "/"
					route(root, "/", {
						"/": {view: lock(function() {i++})}
					})
					var before = i

					mountRedraw.redraw()
					mountRedraw.redraw()
					mountRedraw.redraw()
					mountRedraw.redraw()
					var after = i

					throttleMock.fire()

					o(before).equals(1) // routes synchronously
					o(after).equals(1) // redraws asynchronously
					o(i).equals(2)
				})

				o("m.route.param is available outside of route handlers", function() {
					$window.location.href = prefix + "/"

					route(root, "/1", {
						"/:id" : {
							view : lock(function() {
								o(route.param("id")).equals("1")

								return m("div")
							})
						}
					})

					o(route.param("id")).equals(undefined);
					o(route.param()).deepEquals(undefined);

					return waitCycles(1).then(function() {
						o(route.param("id")).equals("1")
						o(route.param()).deepEquals({id:"1"})
					})
				})

				o("route component is mounted after the route is initially resolved (synchronous)", function() {
					var Component = {
						view: lock(function() {
							// the first rendered vnode is cleared
							o(root.childNodes.length).equals(0)
							return m("span")
						})
					}

					// initial root node
					root.textContent = "foo"
					o(root.childNodes.length).equals(1)
					o(root.childNodes[0].nodeName).equals("#text")
					o(root.childNodes[0].nodeValue).equals("foo")

					// render another vnode first
					var render = coreRenderer($window)
					var vnode = m("a", "loading...")
					render(root, vnode)
					o(root.childNodes.length).equals(1)
					o(root.childNodes[0].nodeName).equals("A")
					o(root.childNodes[0].firstChild.nodeName).equals("#text")
					o(root.childNodes[0].firstChild.nodeValue).equals("loading...")

					// call route() (mount synchronously)
					$window.location.href = prefix + "/"
					route(root, "/", {
						"/" : Component
					})

					// route component is mounted and the first rendered vnode is cleared
					o(root.childNodes.length).equals(1)
					o(root.childNodes[0]).notEquals(vnode.dom)
					o(root.childNodes[0].nodeName).equals("SPAN")
					o(root.childNodes[0].childNodes.length).equals(0)
				})

				o("route component is mounted after the route is initially resolved (render, synchronous)", function() {
					var Component = {
						render: lock(function() {
							// the first rendered vnode is cleared
							o(root.childNodes.length).equals(0)
							return m("span")
						})
					}

					// initial root node
					root.textContent = "foo"
					o(root.childNodes.length).equals(1)
					o(root.childNodes[0].nodeName).equals("#text")
					o(root.childNodes[0].nodeValue).equals("foo")

					// render another vnode first
					var render = coreRenderer($window)
					var vnode = m("a", "loading...")
					render(root, vnode)
					o(root.childNodes.length).equals(1)
					o(root.childNodes[0].nodeName).equals("A")
					o(root.childNodes[0].firstChild.nodeName).equals("#text")
					o(root.childNodes[0].firstChild.nodeValue).equals("loading...")

					// call route() (mount synchronously)
					$window.location.href = prefix + "/"
					route(root, "/", {
						"/" : Component
					})

					// route component is mounted and the first rendered vnode is cleared
					o(root.childNodes.length).equals(1)
					o(root.childNodes[0]).notEquals(vnode.dom)
					o(root.childNodes[0].nodeName).equals("SPAN")
					o(root.childNodes[0].childNodes.length).equals(0)
				})

				o("route component is mounted after the route is initially resolved (onmatch, asynchronous)", function() {
					var Component = {
						view: lock(function() {
							return m("span")
						})
					}

					// check for the order of calling onmatch and render
					var count = 0

					var resolver = {
						onmatch: lock(function() {
							count += 1
							o(count).equals(1)

							// the first rendered vnode is not yet cleared
							o(root.childNodes.length).equals(1)
							o(root.childNodes[0]).equals(vnode.dom)
							o(root.childNodes[0].nodeName).equals("A")
							o(root.childNodes[0].firstChild.nodeName).equals("#text")
							o(root.childNodes[0].firstChild.nodeValue).equals("loading...")
							return Component
						}),
						render: lock(function(vnode) {
							count += 1
							o(count).equals(2)

							// the first rendered vnode is cleared
							o(root.childNodes.length).equals(0)
							return vnode
						})
					}

					// initial root node
					root.textContent = "foo"
					o(root.childNodes.length).equals(1)
					o(root.childNodes[0].nodeName).equals("#text")
					o(root.childNodes[0].nodeValue).equals("foo")

					// render another vnode first
					var render = coreRenderer($window)
					var vnode = m("a", "loading...")
					render(root, vnode)
					o(root.childNodes.length).equals(1)
					o(root.childNodes[0].nodeName).equals("A")
					o(root.childNodes[0].firstChild.nodeName).equals("#text")
					o(root.childNodes[0].firstChild.nodeValue).equals("loading...")

					// call route() (mount asynchronously)
					$window.location.href = prefix + "/"
					route(root, "/", {
						"/" : resolver
					})

					// the first rendered vnode is not yet cleared
					o(root.childNodes.length).equals(1)
					o(root.childNodes[0]).equals(vnode.dom)
					o(root.childNodes[0].nodeName).equals("A")
					o(root.childNodes[0].firstChild.nodeName).equals("#text")
					o(root.childNodes[0].firstChild.nodeValue).equals("loading...")

					// The count of route resolver method calls is still 0
					o(count).equals(0)

					return waitCycles(1).then(function() {
						// route component is mounted and the first rendered vnode is cleared
						o(root.childNodes.length).equals(1)
						o(root.childNodes[0]).notEquals(vnode.dom)
						o(root.childNodes[0].nodeName).equals("SPAN")
						o(root.childNodes[0].childNodes.length).equals(0)

						o(count).equals(2)
					})
				})

				o("error in the route component is thrown and not caught in the initial rendering (#2621)", function() {
					var Component = {
						view: lock(function() {
							throw Error("foo")
						})
					}

					// Errors thrown during redrawing of mounted components are caught in m.mount()
					// and console.error is called.
					// Therefore, spy is used to confirm that console.error is not called
					// when it is first mounted.
					var spy = o.spy(console.error)
					console.error = spy

					$window.location.href = prefix + "/"
					o(function(){
						route(root, "/", {
							"/" : Component
						})
					}).throws("foo")

					o(spy.callCount).equals(0)
				})
			})
		})
	})
})


================================================
FILE: api/tests/test-routerGetSet.js
================================================
"use strict"

// Low-priority TODO: remove the dependency on the renderer here.
var o = require("ospec")
var browserMock = require("../../test-utils/browserMock")
var throttleMocker = require("../../test-utils/throttleMock")

var apiMountRedraw = require("../../api/mount-redraw")
var coreRenderer = require("../../render/render")
var apiRouter = require("../../api/router")

o.spec("route.get/route.set", function() {
	void [{protocol: "http:", hostname: "localhost"}, {protocol: "file:", hostname: "/"}].forEach(function(env) {
		void ["#", "?", "", "#!", "?!", "/foo"].forEach(function(prefix) {
			o.spec("using prefix `" + prefix + "` starting on " + env.protocol + "//" + env.hostname, function() {
				var $window, root, mountRedraw, route, throttleMock

				o.beforeEach(function() {
					$window = browserMock(env)
					throttleMock = throttleMocker()
					$window.setTimeout = setTimeout

					root = $window.document.body

					mountRedraw = apiMountRedraw(coreRenderer($window), throttleMock.schedule, console)
					route = apiRouter($window, mountRedraw)
					route.prefix = prefix
				})

				o.afterEach(function() {
					o(throttleMock.queueLength()).equals(0)
				})

				o("gets route", function() {
					$window.location.href = prefix + "/test"
					route(root, "/test", {"/test": {view: function() {}}})

					o(route.get()).equals("/test")
				})

				o("gets route w/ params", function() {
					$window.location.href = prefix + "/other/x/y/z?c=d#e=f"

					route(root, "/other/x/y/z?c=d#e=f", {
						"/test": {view: function() {}},
						"/other/:a/:b...": {view: function() {}},
					})

					o(route.get()).equals("/other/x/y/z?c=d#e=f")
				})

				o("gets route w/ escaped unicode", function() {
					$window.location.href = prefix + encodeURI("/ö/é/å?ö=ö#ö=ö")

					route(root, "/ö/é/å?ö=ö#ö=ö", {
						"/test": {view: function() {}},
						"/ö/:a/:b...": {view: function() {}},
					})

					o(route.get()).equals("/ö/é/å?ö=ö#ö=ö")
				})

				o("gets route w/ unicode", function() {
					$window.location.href = prefix + "/ö/é/å?ö=ö#ö=ö"

					route(root, "/ö/é/å?ö=ö#ö=ö", {
						"/test": {view: function() {}},
						"/ö/:a/:b...": {view: function() {}},
					})

					o(route.get()).equals("/ö/é/å?ö=ö#ö=ö")
				})

				o("sets path asynchronously", function(done) {
					$window.location.href = prefix + "/a"
					var spy1 = o.spy()
					var spy2 = o.spy()

					route(root, "/a", {
						"/a": {view: spy1},
						"/b": {view: spy2},
					})

					o(spy1.callCount).equals(1)
					o(spy2.callCount).equals(0)
					route.set("/b")
					o(spy1.callCount).equals(1)
					o(spy2.callCount).equals(0)
					setTimeout(function() {
						throttleMock.fire()

						o(spy1.callCount).equals(1)
						o(spy2.callCount).equals(1)
						done()
					})
				})

				o("sets fallback asynchronously", function(done) {
					$window.location.href = prefix + "/b"
					var spy1 = o.spy()
					var spy2 = o.spy()

					route(root, "/a", {
						"/a": {view: spy1},
						"/b": {view: spy2},
					})

					o(spy1.callCount).equals(0)
					o(spy2.callCount).equals(1)
					route.set("/c")
					o(spy1.callCount).equals(0)
					o(spy2.callCount).equals(1)
					setTimeout(function() {
						// Yep, before even the throttle mechanism takes hold.
						o(route.get()).equals("/b")
						setTimeout(function() {
							// Yep, before even the throttle mechanism takes hold.
							o(route.get()).equals("/a")
							throttleMock.fire()

							o(spy1.callCount).equals(1)
							o(spy2.callCount).equals(1)
							done()
						})
					})
				})

				o("exposes new route asynchronously", function(done) {
					$window.location.href = prefix + "/test"
					route(root, "/test", {
						"/test": {view: function() {}},
						"/other/:a/:b...": {view: function() {}},
					})

					route.set("/other/x/y/z?c=d#e=f")
					setTimeout(function() {
						// Yep, before even the throttle mechanism takes hold.
						o(route.get()).equals("/other/x/y/z?c=d#e=f")
						throttleMock.fire()
						done()
					})
				})

				o("exposes new escaped unicode route asynchronously", function(done) {
					$window.location.href = prefix + "/test"
					route(root, "/test", {
						"/test": {view: function() {}},
						"/ö": {view: function() {}},
					})

					route.set(encodeURI("/ö?ö=ö#ö=ö"))
					setTimeout(function() {
						// Yep, before even the throttle mechanism takes hold.
						o(route.get()).equals("/ö?ö=ö#ö=ö")
						throttleMock.fire()
						done()
					})
				})

				o("exposes new unescaped unicode route asynchronously", function(done) {
					$window.location.href = "file://" + prefix + "/test"
					route(root, "/test", {
						"/test": {view: function() {}},
						"/ö": {view: function() {}},
					})

					route.set("/ö?ö=ö#ö=ö")
					setTimeout(function() {
						// Yep, before even the throttle mechanism takes hold.
						o(route.get()).equals("/ö?ö=ö#ö=ö")
						throttleMock.fire()
						done()
					})
				})

				o("exposes new route asynchronously on fallback mode", function(done) {
					$window.location.href = prefix + "/test"
					route(root, "/test", {
						"/test": {view: function() {}},
						"/other/:a/:b...": {view: function() {}},
					})

					route.set("/other/x/y/z?c=d#e=f")
					setTimeout(function() {
						// Yep, before even the throttle mechanism takes hold.
						o(route.get()).equals("/other/x/y/z?c=d#e=f")
						throttleMock.fire()
						done()
					})
				})

				o("sets route via pushState/onpopstate", function(done) {
					$window.location.href = prefix + "/test"
					route(root, "/test", {
						"/test": {view: function() {}},
						"/other/:a/:b...": {view: function() {}},
					})

					setTimeout(function() {
						$window.history.pushState(null, null, prefix + "/other/x/y/z?c=d#e=f")
						$window.onpopstate()

						setTimeout(function() {
							// Yep, before even the throttle mechanism takes hold.
							o(route.get()).equals("/other/x/y/z?c=d#e=f")
							throttleMock.fire()

							done()
						})
					})
				})

				o("sets parameterized route", function(done) {
					$window.location.href = prefix + "/test"
					route(root, "/test", {
						"/test": {view: function() {}},
						"/other/:a/:b...": {view: function() {}},
					})

					route.set("/other/:a/:b", {a: "x", b: "y/z", c: "d", e: "f"})
					setTimeout(function() {
						// Yep, before even the throttle mechanism takes hold.
						o(route.get()).equals("/other/x/y%2Fz?c=d&e=f")
						throttleMock.fire()
						done()
					})
				})

				o("replace:true works", function(done) {
					$window.location.href = prefix + "/test"
					route(root, "/test", {
						"/test": {view: function() {}},
						"/other": {view: function() {}},
					})

					route.set("/other", null, {replace: true})

					setTimeout(function() {
						throttleMock.fire()
						$window.history.back()
						o($window.location.href).equals(env.protocol + "//" + (env.hostname === "/" ? "" : env.hostname) + "/")
						done()
					})
				})

				o("replace:false works", function(done) {
					$window.location.href = prefix + "/test"
					route(root, "/test", {
						"/test": {view: function() {}},
						"/other": {view: function() {}},
					})

					route.set("/other", null, {replace: false})

					setTimeout(function() {
						throttleMock.fire()
						$window.history.back()
						var slash = prefix[0] === "/" ? "" : "/"
						o($window.location.href).equals(env.protocol + "//" + (env.hostname === "/" ? "" : env.hostname) + slash + (prefix ? prefix + "/" : "") + "test")
						done()
					})
				})

				o("state works", function(done) {
					$window.location.href = prefix + "/test"
					route(root, "/test", {
						"/test": {view: function() {}},
						"/other": {view: function() {}},
					})

					route.set("/other", null, {state: {a: 1}})
					setTimeout(function() {
						throttleMock.fire()
						o($window.history.state).deepEquals({a: 1})
						done()
					})
				})
			})
		})
	})
})


================================================
FILE: browser.js
================================================
"use strict"

var m = require("./index")
if (typeof module !== "undefined") module["exports"] = m
else window.m = m


================================================
FILE: docs/code-of-conduct.md
================================================
<!--meta-description
Code of Conduct Covenant for contributors to the Mithril.js project
-->

# Contributor Covenant Code of Conduct

## Our Pledge

In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, gender identity and expression, level of experience,
nationality, personal appearance, race, religion, or sexual identity and
orientation.

## Our Standards

Examples of behavior that contributes to creating a positive environment
include:

* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members

Examples of unacceptable behavior by participants include:

* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
  address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
  professional setting

## Our Responsibilities

Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.

Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.

## Scope

This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.

## Enforcement

Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at [contact@claudiameadows.dev](mailto:contact@claudiameadows.dev?subject=Mithril%20Code%20of%20Conduct). All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.

Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.

As a general policy, we generally do not disclose any particular action taken in accordance with this Code of Conduct beyond those who need to know.

## Attribution

This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at [https://contributor-covenant.org/version/1/4][version]

[homepage]: https://contributor-covenant.org
[version]: https://contributor-covenant.org/version/1/4/


================================================
FILE: docs/contributing.md
================================================
<!--meta-description
Contribution guide for Mithril.js
-->
# Contributing FAQs

- [How do I go about contributing ideas or new features?](#how-do-i-go-about-contributing-ideas-or-new-features?)
- [How should I report bugs?](#how-should-i-report-bugs?)
- [How do I send a pull request?](#how-do-i-send-a-pull-request?)
- [I'm submitting a PR. How do I run tests?](#i'm-submitting-a-pr-how-do-i-run-tests?)
- [How do I build Mithril.js?](#how-do-i-build-mithril?)
- [Is there a style guide?](#is-there-a-style-guide?)
- [How do I embed live previews in docs?](#how-do-I-embed-live-previews-in-docs?)
- [Why do tests mock the browser APIs?](#why-do-tests-mock-the-browser-apis?)
- [Why does Mithril.js use its own testing framework and not Mocha/Jasmine/Tape?](#why-does-mithril-use-its-own-testing-framework-and-not-mochajasminetape?)
- [Why doesn't the Mithril.js codebase use ES6 via Babel or Bublé? Would a PR to upgrade be welcome?](#why-doesn't-the-mithril-codebase-use-es6-via-babel-or-bublé?-would-a-pr-to-upgrade-be-welcome?)
- [Why doesn't the Mithril.js codebase use trailing semi-colons? Would a PR to add them be welcome?](#why-doesn't-the-mithril-codebase-use-trailing-semi-colons?-would-a-pr-to-add-them-be-welcome?)
- [Why does the Mithril.js codebase use a mix of `instanceof` and `typeof` checks instead of `Object.prototype.toString.call`, `Array.isArray`, etc? Would a PR to refactor those checks be welcome?](#why-does-the-mithril-codebase-use-a-mix-of-instanceof-and-typeof-checks-instead-of-objectprototypetostringcall,-arrayisarray,-etc?-would-a-pr-to-refactor-those-checks-be-welcome?)
- [What should I know in advance when attempting a performance related contribution?](#What-should-I-know-in-advance-when-attempting-a-performance-related-contribution?)
- [Do you all accept donations?](#do-you-all-accept-donations?)

## How do I go about contributing ideas or new features?

Create an [issue thread on GitHub](https://github.com/MithrilJS/mithril.js/issues/new) to suggest your idea so the community can discuss it.

If the consensus is that it's a good idea, the fastest way to get it into a release is to send a pull request. Without a PR, the time to implement the feature will depend on the bandwidth of the development team and its list of priorities.



## How should I report bugs?

Ideally, the best way to report bugs is to provide a small snippet of code where the issue can be reproduced (via jsfiddle, jsbin, a gist, etc). Even better would be to submit a pull request with a fix and tests. If you don't know how to test your fix, or lint or whatever, submit anyways, and we can help you.



## How do I send a pull request?

To send a pull request:

- fork the repo (button at the top right in GitHub)
- clone the forked repo to your computer (green button in GitHub)
- Switch to the `main` branch (run `git checkout main`)
- create a feature branch (run `git checkout -b the-feature-branch-name`)
- make your changes
- run the tests (run `npm test`)
- push your changes to your fork
- submit a pull request (go to the pull requests tab in GitHub, click the green button and select your feature branch)



## I'm submitting a PR. How do I run tests?

After having run `npm install` (a one-time operation), run `npm run test` from the command line to run all tests.

While testing, you can modify a test to use `o.only(description, test)` instead of `o(description, test)` if you wish to run only a specific test to speed up your debugging experience. Don't forget to remove the `.only` after you're done!



## How do I build Mithril.js?

If all you're trying to do is run examples in the codebase, you don't need to build Mithril.js, you can just open the various html files and things should just work.

To generate the bundled file for testing, run `npm run dev` from the command line. To generate the minified file, run `npm run build`.



## Is there a style guide?

Yes, there's an `eslint` configuration, but it's not strict about formatting at all. If your contribution passes `npm run lint`, it's good enough for a PR (and it can still be accepted even if it doesn't pass).

Spacing and formatting inconsistencies may be fixed after the fact, and we don't want that kind of stuff getting in the way of contributing.



## How do I embed live previews in docs?

Any code tag marked as `js` and not `javascript` will automatically be wrapped in a live Flems preview.



## Why do tests mock the browser APIs?

Most notoriously, because it's impossible to test the router and some side effects properly otherwise. Also, mocks allow the tests to run under Node.js without requiring heavy dependencies like PhantomJS/ChromeDriver/JSDOM.

Another important reason is that it allows us to document browser API quirks via code, through the tests for the mocks.



## Why does Mithril.js use its own testing framework and not Mocha/Jasmine/Tape?

Mainly to avoid requiring dependencies. `ospec` is customized to provide only essential information for common testing workflows (namely, no spamming ok's on pass, and accurate noiseless errors on failure)



## Why doesn't the Mithril.js codebase use ES6 via Babel or Bublé? Would a PR to upgrade be welcome?

Being able to run Mithril.js' raw source code in all supported browsers is a requirement for all browser-related modules in this repo. In addition, transpiled code is generally much bulkier.



## Why doesn't the Mithril.js codebase use trailing semi-colons? Would a PR to add them be welcome?

I don't use them. Adding them means the semi-colon usage in the codebase will eventually become inconsistent. Besides, [we aren't the only one who've decided to drop the semicolon](https://standardjs.com/#who-uses-javascript-standard-style). (We don't use Standard, though.)



## Why does the Mithril.js codebase use a mix of `instanceof` and `typeof` checks instead of `Object.prototype.toString.call`, `Array.isArray`, etc? Would a PR to refactor those checks be welcome?

Mithril.js avoids peeking at objects' [[class]] string for performance considerations. Many type checks are seemingly inconsistent, weird or convoluted because those specific constructs demonstrated the best performance profile in benchmarks compared to alternatives.

Type checks are generally already irreducible expressions and having micro-modules for type checking subroutines would add maintenance overhead.



## What should I know in advance when attempting a performance related contribution?

You should be trying to reduce the number of DOM operations or reduce algorithmic complexity in a hot spot. Anything else is likely a waste of time. Specifically, micro-optimizations like caching array lengths, caching object property values and inlining functions won't have any positive impact in modern JavaScript engines.

Keep object properties consistent (i.e. ensure the data objects always have the same properties and that properties are always in the same order) to allow the engine to keep using JIT'ed structs instead of hashmaps. Always place null checks first in compound type checking expressions to allow the JavaScript engine to optimize to type-specific code paths. Prefer for loops over Array methods and try to pull conditionals out of loops if possible.



## Do you all accept donations?

Yes, we do, over at [our OpenCollective page](https://opencollective.com/mithriljs). We don't actively seek donations, but they are very much appreciated and are used to support development and related expenses. Both one-time and recurring donations are accepted.


================================================
FILE: docs/credits.md
================================================
<!--meta-description
List of especially notable contributors to Mithril.js
-->

# Credits

Mithril.js was originally written by Leo Horie, but it is where it is today thanks to the hard work and great ideas of many people.

Special thanks to:

- Pat Cavit, who exposed most of the public API for Mithril.js 1.0, brought in test coverage and automated the publishing process
- Claudia Meadows, who brought in linting, modernized the test suite and has been a strong voice in design discussions
- Zoli Kahan, who replaced the original Promise implementation with one that actually worked properly
- Alec Embke, who single-handedly wrote the JSON-P implementation
- Barney Carroll, who suggested many great ideas and relentlessly pushed Mithril.js to the limit to uncover design issues prior to Mithril.js 1.0
- Dominic Gannaway, who offered insanely meticulous technical insight into rendering performance
- Boris Letocha, whose search space reduction algorithm is the basis for Mithril.js' virtual DOM engine
- Joel Richard, whose monomorphic virtual DOM structure is the basis for Mithril.js' vnode implementation
- Simon Friis Vindum, whose open source work was an inspiration to many design decisions for Mithril.js 1.0
- Boris Kaul, for his awesome work on the benchmarking tools used to develop Mithril.js
- Leon Sorokin, for writing a DOM instrumentation tool that helped improve performance in Mithril.js 1.0
- Jordan Walke, whose work on React was prior art to the implementation of keys in Mithril.js
- Pierre-Yves Gérardy, who consistently makes high quality contributions
- Gyandeep Singh, who contributed significant IE performance improvements

Other people who also deserve recognition:

- Arthur Clemens - creator of [Polythene](https://github.com/ArthurClemens/Polythene) and the [HTML-to-Mithril converter](https://arthurclemens.github.io/mithril-template-converter/index.html)
- Stephan Hoyer - creator of [mithril-node-render](https://github.com/StephanHoyer/mithril-node-render), [mithril-query](https://github.com/StephanHoyer/mithril-query) and [mithril-source-hint](https://github.com/StephanHoyer/mithril-source-hint)
- the countless people who have reported and fixed bugs, participated in discussions, and helped promote Mithril.js


================================================
FILE: docs/recent-changes.md
================================================

# Release v2.3.8

### Patch Changes

#### [refactor execSelector (@kfule)](https://github.com/MithrilJS/mithril.js/pull/3056)


#### [Make `vnode.domSize` assignment consistent between create and update. (@kfule)](https://github.com/MithrilJS/mithril.js/pull/3055)

This PR makes the code and behavior of create and update processes more consistent.
#### [Bump rimraf from 6.0.1 to 6.1.0 in the normal group (@dependabot[bot])](https://github.com/MithrilJS/mithril.js/pull/3054)

Bumps the normal group with 1 update: [rimraf](https://github.com/isaacs/rimraf).  Updates `rimraf` from 6.0.1 to 6.1.0.  Changelog.  Sourced from rimraf's changelog.
#### [Fix URI decoder bug and reduce bundle size through module tailoring and cleanup (@kfule)](https://github.com/MithrilJS/mithril.js/pull/3050)

This fixes the URI decoder used in the Router to decode more strictly.
#### [refactor `Vnode.normalizeChildren` (@kfule)](https://github.com/MithrilJS/mithril.js/pull/3052)

`Vnode.normalizeChildren` now preallocates the array length and performs key-consistency checks after normalization.
#### [Bump actions/setup-node from 5 to 6 in the normal group (@dependabot[bot])](https://github.com/MithrilJS/mithril.js/pull/3053)

Bumps the normal group with 1 update: [actions/setup-node](https://github.com/actions/setup-node).  Updates `actions/setup-node` from 5 to 6.  Release notes.
#### [Bump actions/setup-node from 4 to 5 in the normal group (@dependabot[bot])](https://github.com/MithrilJS/mithril.js/pull/3047)

Bumps the normal group with 1 update: [actions/setup-node](https://github.com/actions/setup-node).  Updates `actions/setup-node` from 4 to 5.  Release notes.
#### [docs: edited the link to the build badge (@Olexandr88)](https://github.com/MithrilJS/mithril.js/pull/3045)

# Release v2.3.7

### Patch Changes

#### [Make the attrs of non-element vnodes always non-null. (@kfule)](https://github.com/MithrilJS/mithril.js/pull/3042)

In #3041, it seemed that the case of non-element vnodes was not fully considered in terms of not breaking existing behavior.

# Release v2.3.6

### Patch Changes

#### [Make the attrs of non-element vnodes always non-null. (@kfule)](https://github.com/MithrilJS/mithril.js/pull/3042)

In #3041, it seemed that the case of non-element vnodes was not fully considered in terms of not breaking existing behavior.

# Release v2.3.5

### Patch Changes

#### [Assorted Performance Improvements (@kfule)](https://github.com/MithrilJS/mithril.js/pull/3041)

This PR improves performance through the following changes: Adoption of the spread syntax, which can be optimized in modern browsers.
#### [Bump actions/checkout from 4 to 5 in the normal group (@dependabot[bot])](https://github.com/MithrilJS/mithril.js/pull/3039)

Bumps the normal group with 1 update: [actions/checkout](https://github.com/actions/checkout).  Updates `actions/checkout` from 4 to 5.  Release notes.

# Release v2.3.4

### Patch Changes

#### [Fix the error message selection condition (@dead-claudia)](https://github.com/MithrilJS/mithril.js/pull/3037)

The previous condition was basically "if this is non-nullish or a boolean".  That "or a boolean" is very obviously redundant.
#### [bundler: fix mangled comments and double suffixes (@kfule)](https://github.com/MithrilJS/mithril.js/pull/3032)

This PR removes unnecessary suffixes from comments in the bundle file.  It also fixes the strange double suffix (`mountRedraw00`).

# Release v2.3.3

### Patch Changes

#### [router: delay mounting RouterRoot until the first route is resolved (fixes #2621) (@kfule)](https://github.com/MithrilJS/mithril.js/pull/3030)

This PR delays the initial mounting of the router component until after the route has been resolved.
#### [Bump glob from 11.0.2 to 11.0.3 in the normal group (@dependabot[bot])](https://github.com/MithrilJS/mithril.js/pull/3029)

Bumps the normal group with 1 update: [glob](https://github.com/isaacs/node-glob).  Updates `glob` from 11.0.2 to 11.0.3.  Commits.  af2e7ce 11.0.3.

# Release v2.3.2

### Patch Changes

#### [Refactor router, fixes #2505 and #2778 (@kfule)](https://github.com/MithrilJS/mithril.js/pull/3027)

This PR refactors the router code to fix two issues (#2505 and #2778).

# Release v2.3.1

### Patch Changes

#### [set trailing slash optional in route matching (@touletan)](https://github.com/MithrilJS/mithril.js/pull/3025)

Regexp has been updated to set trailing slash as optional in route matching.  link to issue 3024.  New test has been added.

# Release v2.3.0

### Minor Changes

#### [feat: Make redraws when Promises returned by event handlers are completed (@kfule)](https://github.com/MithrilJS/mithril.js/pull/3020)

This PR allows redraw on completion of the async event handler.  This PR makes redraws when Promises returned by event handlers are completed.
   
### Patch Changes

#### [Allow additional async redraw even if the first redraw is skipped (@kfule)](https://github.com/MithrilJS/mithril.js/pull/3021)

This PR allows asynchronous redraw processing even if the first redraw is skipped by setting `event.redraw=false` before await in the async function.
#### [Bump glob from 11.0.1 to 11.0.2 in the normal group (@dependabot[bot])](https://github.com/MithrilJS/mithril.js/pull/3019)

Bumps the normal group with 1 update: [glob](https://github.com/isaacs/node-glob).  Updates `glob` from 11.0.1 to 11.0.2.  Commits.  fd61f24 11.0.2.
#### [Fix badge for build status (@kfule)](https://github.com/MithrilJS/mithril.js/pull/3015)

The URL for the Shields.io badge for build status has been corrected.

# Release v2.2.15

### Patch Changes

#### [[refactor] Limit the condition of the option tag to `selected` attribute in isFormAttribute() (@kfule)](https://github.com/MithrilJS/mithril.js/pull/3011)

This PR limits the evaluation of whether a tag is `option` to only when setting the `selected` attribute.
#### [test-perf: Load Benckmark.js first in Node.js (@kfule)](https://github.com/MithrilJS/mithril.js/pull/3008)

Since Node21, global.navigator has been implemented, and together with browserMock, Benchmark.js incorrectly identifies the execution environment as a browser.

# Release v2.2.14

### Patch Changes

#### [Improve handling of is-elements and Fix tiny bugs of setAttr()/updateStyle() (@kfule)](https://github.com/MithrilJS/mithril.js/pull/2988)

Fixes a few tiny bugs in attributes and style properties updates, and improves handling of is-elements in updateNode().
#### [domFor: always get generation from delayedRemoval instead of parameter (@kfule)](https://github.com/MithrilJS/mithril.js/pull/3007)

The `generation` of domFor is no longer passed as a parameter.  This allows domFor to work well in onbeforeremove and onremove and reduces the amount of code.
#### [render: wrap stateResult and attrsResult in Promise.resolve(), fix #2592 (@kfule)](https://github.com/MithrilJS/mithril.js/pull/3005)

This PR wraps the return value of onbeforeremove in Promise.resolve().  This ensures that thenable objects are also always processed asynchronously.  fix #2592.

# Release v2.2.13

### Patch Changes

#### [Fix form checkValidity(), remove vnode.dom === .activeElement from setAttr() (Continued from #2257) (@kfule)](https://github.com/MithrilJS/mithril.js/pull/3002)

Remove vnode.dom === activeElement(vnode.dom) from setAttribute() to fix validityCheck(), to fix https://github.com/MithrilJS/mithril.js/issues/2256.
#### [Bump glob from 11.0.0 to 11.0.1 in the normal group (@dependabot[bot])](https://github.com/MithrilJS/mithril.js/pull/3001)

Bumps the normal group with 1 update: [glob](https://github.com/isaacs/node-glob).  Updates `glob` from 11.0.0 to 11.0.1.  Commits.  148ef61 11.0.1.

# Release v2.2.12

### Patch Changes

#### [disable Terser's "reduce_funcs" option for performance (@kfule)](https://github.com/MithrilJS/mithril.js/pull/3000)

Terser's  “reduce_funcs” option seems to degrade performance.  So, disable it.
#### [Bump chokidar from 4.0.1 to 4.0.3 in the normal group across 1 directory (@dependabot[bot])](https://github.com/MithrilJS/mithril.js/pull/2998)

Bumps the normal group with 1 update in the / directory: [chokidar](https://github.com/paulmillr/chokidar).  Updates `chokidar` from 4.0.1 to 4.0.3.  Release notes.

# Release v2.2.11

### Patch Changes

#### [Use new pr-release prerelease hook (Fixes #2987) (@JAForbes)](https://github.com/MithrilJS/mithril.js/pull/2996)

Per @dead-claudia's suggestion, pr-release now allows you to invoke a custom command before creating the github release.
#### [updateStyle(): use setProperty() when css vars and dashed-properties, fixes #2989 (@kfule)](https://github.com/MithrilJS/mithril.js/pull/2991)

This PR changes updateStyle() to use setProperty() for dashed-properties.  This PR maybe fixes #2989.
#### [Delete .github/ISSUE_TEMPLATE/0-docs.yml (@dead-claudia)](https://github.com/MithrilJS/mithril.js/pull/2993)

Do a much better job discouraging filing docs bugs here.

# Release v2.2.10

### Patch Changes

#### [[refactor] Performance improvement of updateStyle() (@kfule)](https://github.com/MithrilJS/mithril.js/pull/2985)

This is a refactoring to improve the performance of `updateStyle()`.

# Release v2.2.9

### Patch Changes

#### [[refactor] Refactoring of hyperscript.js and render.js, including performance improvements (@kfule)](https://github.com/MithrilJS/mithril.js/pull/2983)

Refactor hyperscript.js and render.js.  In particular, the replacement of fix #2622 appears to have significantly improved the performance regression.

# Release v2.2.8

### Patch Changes

#### [m.domFor(): workaround for unintentional mangling. Fix #2842 (@kfule)](https://github.com/MithrilJS/mithril.js/pull/2981)

Refactoring of domFor() for the internal bundler.  https://github.com/MithrilJS/mithril.js/blob/cfa890f68571df1ab8543097f7fa61c34ee93683/mithril.js#L157.
#### [Drop Istanbul to kill install warnings (@dead-claudia)](https://github.com/MithrilJS/mithril.js/pull/2979)

Title's pretty self-explanatory.  Also, this isn't really used much in practice.  From a local run: ```.  $ npm ci.

# Release v2.2.7

### Patch Changes

#### [m.domFor(): workaround for unintentional mangling. Fix #2842 (@kfule)](https://github.com/MithrilJS/mithril.js/pull/2981)

Refactoring of domFor() for the internal bundler.  https://github.com/MithrilJS/mithril.js/blob/cfa890f68571df1ab8543097f7fa61c34ee93683/mithril.js#L157.
#### [Drop Istanbul to kill install warnings (@dead-claudia)](https://github.com/MithrilJS/mithril.js/pull/2979)

Title's pretty self-explanatory.  Also, this isn't really used much in practice.  From a local run: ```.  $ npm ci.

# Release v2.2.6

### Patch Changes

#### [m.domFor(): workaround for unintentional mangling. Fix #2842 (@kfule)](https://github.com/MithrilJS/mithril.js/pull/2981)

Refactoring of domFor() for the internal bundler.  https://github.com/MithrilJS/mithril.js/blob/cfa890f68571df1ab8543097f7fa61c34ee93683/mithril.js#L157.
#### [Drop Istanbul to kill install warnings (@dead-claudia)](https://github.com/MithrilJS/mithril.js/pull/2979)

Title's pretty self-explanatory.  Also, this isn't really used much in practice.  From a local run: ```.  $ npm ci.

# Release v2.2.5

### Patch Changes

#### [Bump the normal group across 1 directory with 2 updates (@dependabot[bot])](https://github.com/MithrilJS/mithril.js/pull/2976)

Bumps the normal group with 2 updates in the / directory: [chokidar](https://github.com/paulmillr/chokidar) and [eslint](https://github.com/eslint/eslint).
#### [Cleaning up code by making vnode.attrs always non-null (@kfule)](https://github.com/MithrilJS/mithril.js/pull/2977)

Commit f9e5163 made vnode.attrs always non-null, so there is no need for code to make vnode.attrs null or assume vnode.attrs is null.

# Release v2.2.4

### Patch Changes

#### [Bump gh-pages from 2.1.1 to 5.0.0 (@dependabot[bot])](https://github.com/MithrilJS/mithril.js/pull/2890)

Bumps [gh-pages](https://github.com/tschaub/gh-pages) from 2.1.1 to 5.0.0.  Release notes.  Sourced from gh-pages's releases.  v5.0.0.
#### [Bump @babel/parser from 7.7.5 to 7.25.6 (@dependabot[bot])](https://github.com/MithrilJS/mithril.js/pull/2959)

Bumps [@babel/parser](https://github.com/babel/babel/tree/HEAD/packages/babel-parser) from 7.7.5 to 7.25.6.  Release notes.  Sourced from @​babel/parser's releases.
#### [Bump minimatch from 3.0.4 to 3.1.2 (@dependabot[bot])](https://github.com/MithrilJS/mithril.js/pull/2816)

Bumps [minimatch](https://github.com/isaacs/minimatch) from 3.0.4 to 3.1.2.  Commits.  699c459 3.1.2.  2f2b5ff fix: trim pattern.  25d7c0d 3.1.1.
#### [Bump yaml and lint-staged (@dependabot[bot])](https://github.com/MithrilJS/mithril.js/pull/2830)

Bumps [yaml](https://github.com/eemeli/yaml) to 2.2.2 and updates ancestor dependency [lint-staged](https://github.com/okonet/lint-staged).
#### [Bump gh-pages from 5.0.0 to 6.1.1 (@dependabot[bot])](https://github.com/MithrilJS/mithril.js/pull/2958)

Bumps [gh-pages](https://github.com/tschaub/gh-pages) from 5.0.0 to 6.1.1.  Release notes.  Sourced from gh-pages's releases.  v6.1.1.  Fixes.
#### [Bump glob from 7.1.4 to 11.0.0 (@dependabot[bot])](https://github.com/MithrilJS/mithril.js/pull/2957)

Bumps [glob](https://github.com/isaacs/node-glob) from 7.1.4 to 11.0.0.  Changelog.  Sourced from glob's changelog.  changeglob.  11.0.  Drop support for node before v20.
#### [Bump rimraf from 3.0.2 to 6.0.1 (@dependabot[bot])](https://github.com/MithrilJS/mithril.js/pull/2960)

Bumps [rimraf](https://github.com/isaacs/rimraf) from 3.0.2 to 6.0.1.  Changelog.  Sourced from rimraf's changelog.  6.0.  Drop support for nodes before v20.
#### [Bump lint-staged from 13.2.1 to 15.2.10 (@dependabot[bot])](https://github.com/MithrilJS/mithril.js/pull/2961)

[//]: # (dependabot-start).  ⚠️  **Dependabot is rebasing this PR** ⚠️.  Rebasing might not happen immediately, so don't worry if this takes some time.
#### [Revise issue templates (@dead-claudia)](https://github.com/MithrilJS/mithril.js/pull/2955)


#### [Update ospec and a few other dependencies (@dead-claudia)](https://github.com/MithrilJS/mithril.js/pull/2907)

1.  Update ospec to the version I just published.  2.
#### [Fix some outstanding bugs in the docs linter. (@dead-claudia)](https://github.com/MithrilJS/mithril.js/pull/2906)

Missed an edge case in the task queue, and I also wanted to fully dedupe network requests.  Locally it passes.
#### [Rewrite docs linter, ease JSFiddle request debugging (@dead-claudia)](https://github.com/MithrilJS/mithril.js/pull/2904)

I'll defer to the commit descriptions.  They're self-descriptive.  The first diff is quite large.
#### [Update vnodes.md (@dead-claudia)](https://github.com/MithrilJS/mithril.js/pull/2903)

Fix a broken link.  Did some further digging (it's been a while since I've played with the scripts) and found that the JSFiddle errors are just warnings.
#### [Migrate to Node 20, clean up workflows (@dead-claudia)](https://github.com/MithrilJS/mithril.js/pull/2902)

**Note: ignore the commits.  It's a mess.  Just read the combined diff - the PR itself is the standalone unit.  I plan to squash this as I merge anyways.**.
#### [Remove dependance on global window and document (@KoryNunn)](https://github.com/MithrilJS/mithril.js/pull/2897)

Use window and document from render target instead of using globals.  This makes unit and intergration testing much easier.
#### [Bump braces from 3.0.2 to 3.0.3 (@dependabot[bot])](https://github.com/MithrilJS/mithril.js/pull/2896)

Bumps [braces](https://github.com/micromatch/braces) from 3.0.2 to 3.0.3.  Commits.  74b2db2 3.0.3.  88f1429 update eslint.  lint, fix unit tests.
#### [Tweak docs with warning to fix #2508 (@dead-claudia)](https://github.com/MithrilJS/mithril.js/pull/2895)

[z] Documentation change.  [z] My change requires a change to the documentation.  [z] I have updated the documentation accordingly.
#### [Bump qs from 6.5.2 to 6.5.3 (@dependabot[bot])](https://github.com/MithrilJS/mithril.js/pull/2807)

Bumps [qs](https://github.com/ljharb/qs) from 6.5.2 to 6.5.3.  Changelog.  Sourced from qs's changelog.  6.5.3.  [Fix] parse: ignore __proto__ keys (#428).
#### [Temporarily host REM on fly to fix the docs (@JAForbes)](https://github.com/MithrilJS/mithril.js/pull/2893)

Fixes REM examples in the docs.  The documentation currently has a dead link as REM is no longer hosted on heroku.
#### [Move from individual code owners to just pinging all collaborators (@dead-claudia)](https://github.com/MithrilJS/mithril.js/pull/2844)

Most collaborators have commit access, and it'd make it a little easier (and more likely) for pull requests to get reviewed.
#### [docs: absolute url in version selector to avoid 404 errors (2 of 2) (@tbreuss)](https://github.com/MithrilJS/mithril.js/pull/2839)

Fixes #2832 (2 of 2 pull requests).  See my comment at https://github.com/MithrilJS/mithril.js/pull/2835#issuecomment-1535657892.
#### [fix markdown editor example, bump marked.js version up (@tbreuss)](https://github.com/MithrilJS/mithril.js/pull/2848)

Fixes the strange behavior of markdown editor example.  Using newest version of marked.js, fixed strange behavior of markdown editor example.  See #2845.
#### [Bump word-wrap from 1.2.3 to 1.2.4 (@dependabot[bot])](https://github.com/MithrilJS/mithril.js/pull/2856)

Bumps [word-wrap](https://github.com/jonschlinkert/word-wrap) from 1.2.3 to 1.2.4.  Release notes.  Sourced from word-wrap's releases.  1.2.4.  What's Changed.
#### [Add missing `m.censor` to API navigation (@dead-claudia)](https://github.com/MithrilJS/mithril.js/pull/2843)

Not sure how I forgot about this when I added the method.
#### [docs: fix regex for parsing page title (@tbreuss)](https://github.com/MithrilJS/mithril.js/pull/2837)

Fixes https://github.com/MithrilJS/mithril.js/issues/2833.  I tested the generated documentation on my dev machine successfully.
#### [docs: fix broken anchor link on github/npm (@tbreuss)](https://github.com/MithrilJS/mithril.js/pull/2838)

Fixed a not working anchor link on github and npm by removing the question mark.
#### [hyperscript: handles shared empty attrs, fixes #2821 (@kfule)](https://github.com/MithrilJS/mithril.js/pull/2822)

Whenever there are selector-derived attrs, the attrs object will be regenerated and not shared.
#### [Fix typos in `stream()` docs (@mtsknn)](https://github.com/MithrilJS/mithril.js/pull/2825)

Noticed these typos while reading through the page.
#### [Bump async from 2.6.3 to 2.6.4 (@dependabot[bot])](https://github.com/MithrilJS/mithril.js/pull/2815)

Bumps [async](https://github.com/caolan/async) from 2.6.3 to 2.6.4.  Changelog.  Sourced from async's changelog.  v2.6.4.

# Release v2.2.3

### Patch Changes

#### [Use markdown for the README badges (@pygy)](https://github.com/MithrilJS/mithril.js/pull/2773)

Use markdown for the README badges.

# Release v2.2.2

### Patch Changes

#### [Use markdown for the README badges (@pygy)](https://github.com/MithrilJS/mithril.js/pull/2773)

Use markdown for the README badges.

# Release v2.2.1

### Patch Changes

#### [Move the chat to Zulip (@pygy)](https://github.com/MithrilJS/mithril.js/pull/2771)

This updates the documentation to link to the new Zulip chat room.

# Release v2.2.0

### Minor Changes

#### [m.censor: work around a bunder bug (@kfule)](https://github.com/MithrilJS/mithril.js/pull/2752)

The internal bundler sometimes mangles the words in RegExp literals incorrectly.  Please see below.
#### [Warn about reusing mutated attrs object - fixes #2719 (@StephanHoyer)](https://github.com/MithrilJS/mithril.js/pull/2722)


#### [Send URLSearchParams as request body without extra configuration (@Coteh)](https://github.com/MithrilJS/mithril.js/pull/2695)

This PR fixes an oddity I noticed in the way `m.request` handles `URLSearchParams` object.  It now handles it in the same sort of way XHR and Fetch do it.
#### [Add `params:` to `m.route.Link`, fix docs (@dead-claudia)](https://github.com/MithrilJS/mithril.js/pull/2537)

Add `params:` to `m.route.Link`.  Minor fix to docs to reflect reality with `m.route.Link`'s `disabled:` attribute.
#### [Allow Mithril to be loaded in non-browser environments without modification (@dead-claudia)](https://github.com/MithrilJS/mithril.js/pull/2633)

Recast the global reads to all be guarded with `typeof`, so that if they aren't defined, they're just `null`.
#### [Add a `m.Fragment = "["` utility for JSX users. (@dead-claudia)](https://github.com/MithrilJS/mithril.js/pull/2744)

The title says it all, and the diff's obvious.  Resolves https://github.com/MithrilJS/mithril.js/issues/2640 and probably others.
   
### Patch Changes

#### [Enable --minimize-semver-change for pr-release (@JAForbes)](https://github.com/MithrilJS/mithril.js/pull/2769)

Minimizes semver changes on release to the minimum required version bump to satisfy major/minor/patch semver ranges.  Minimizes the semver change so that.
#### [Clean up m.route.Link (@barneycarroll)](https://github.com/MithrilJS/mithril.js/pull/2768)

An attempt at better demonstrating `m.route.Link` with less text.  Fixes #2767.
#### [Runtime-deprecate ospec, change `change-log` to `changelog`, fix a few assorted bugs (@dead-claudia)](https://github.com/MithrilJS/mithril.js/pull/2578)

This PR is in two parts: 1.  Revise the build system and some of the local dev setup.  Fully split ospec from the repo, and add it as a dependency.
#### [Add meta description to docs (@StephanHoyer)](https://github.com/MithrilJS/mithril.js/pull/2316)

rework of #2149.  added a meta description parser and meta descriptions to all docs pages.  because google.  built the docs, inspected the output manually.
#### [Fixed badges, consistent naming of Mithril.js (@tbreuss)](https://github.com/MithrilJS/mithril.js/pull/2750)

use consistent naming of Mithril.js.  fix badges in README.  Fixes issue #2749.
#### [Catch malformed URI Components (@jdiderik)](https://github.com/MithrilJS/mithril.js/pull/2711)

Fix for error thrown when a value contains non-valid / malformed URI Component.  Example: test=%c5%a1%e8ZM%80%82H.  will throw "URI malformed".
#### [Correctly handle invalid escapes in routes based on 0a5ead31c9fbd7b153c521c7f9d3df7bf826ce6c (@StephanHoyer)](https://github.com/MithrilJS/mithril.js/pull/2743)

fixes #2061.  @dead-claudia I just redid your change but slightly different in order to handle a mix of wrong and right encodings properly.
#### [Standardise vnode text representation (@barneycarroll)](https://github.com/MithrilJS/mithril.js/pull/2670)

This addresses the crucial feature of #2669: text is always represented as virtual text nodes, never as a `vnode.text`.
#### [Issue 2624 no content 204 parse (@Evoke-PHP)](https://github.com/MithrilJS/mithril.js/pull/2641)

Added guard so that JSON.parse does not fail on IE11 with no content empty string being parsed.  Fixes https://github.com/MithrilJS/mithril.js/issues/2624.
#### [[m.request] work around a bundler bug, fix #2647 (@pygy)](https://github.com/MithrilJS/mithril.js/pull/2655)

The bundler mangles identifier-like strings within RegExps, this works around the problem by not using such RegExps.
#### [Reject request on XHR timeout (@kevinfiol)](https://github.com/MithrilJS/mithril.js/pull/2646)

Derived from PR #2581.  Allows requests to properly reject on event of a timeout.
#### [Remove extra isLifecycleMethod call from removeAttr (@ZeikJT)](https://github.com/MithrilJS/mithril.js/pull/2594)

Removing an extra isLifecycleMethod in the removeAttr method, it isn't needed since it's already checked on the previous line.
#### [Fix #2601 (@dead-claudia)](https://github.com/MithrilJS/mithril.js/pull/2603)

Fix issue where ending a stream in the middle of a stream callback would result in erroneous parent stream state for the rest of that emit.  Fixes #2601.
#### [Add streams to releases again, include minified bundle, drop internal stuff from npm (@dead-claudia)](https://github.com/MithrilJS/mithril.js/pull/2539)

Add `stream/stream.js` to releases again.  Add `stream/stream.min.js` now that the process is remotely sane now.
#### [Make errors and their messages more accurate and helpful (@dead-claudia)](https://github.com/MithrilJS/mithril.js/pull/2536)

I updated error messages to be much more helpful.
#### [Fix assertion descriptions (@soulofmischief)](https://github.com/MithrilJS/mithril.js/pull/2405)

I moved the return statement to the end of define() so that it returns even if the comparison fails.
#### [Fix branch target (@dead-claudia)](https://github.com/MithrilJS/mithril.js/pull/2765)

https://github.com/MithrilJS/mithril.js/runs/6199543939?check_suite_focus=true.
#### [Automate mithril's release workflow (@JAForbes)](https://github.com/MithrilJS/mithril.js/pull/2760)

Automated releases, pre-releases, (code) rollbacks and recovery, npm publishing, change log management just by using normal github flow.
#### [rework jsx docs (@StephanHoyer)](https://github.com/MithrilJS/mithril.js/pull/2758)


#### [Add Simple Application Flems Supporting v2.0.4 and up (@tbreuss)](https://github.com/MithrilJS/mithril.js/pull/2751)

Added Flems for Simple Application supporting v2.0.4 of Mithril.js.  Fixes Issue #2710.
#### [Make example work with webpack v5.69.1 (@StephanHoyer)](https://github.com/MithrilJS/mithril.js/pull/2757)

fixes #2634.
#### [2604: correct and move text about statements in view method (@kevinfiol)](https://github.com/MithrilJS/mithril.js/pull/2748)

Addresses #2604.
#### [Fix lint errors (@StephanHoyer)](https://github.com/MithrilJS/mithril.js/pull/2745)


#### [WIP: Update modularisation details in Installation docs (@orbitbot)](https://github.com/MithrilJS/mithril.js/pull/2620)

added link to flems.io as an easier way to just try out the framework.  -.  Documentation has grown a bit stale.
#### [Added power support for the travis.yml file with ppc64le (@sreekanth370)](https://github.com/MithrilJS/mithril.js/pull/2644)

Added power support for the travis.yml file with ppc64le.  This is part of the Ubuntu distribution for ppc64le.
#### [Updated babel/webpack docs to work with latest versions (@pereriksson)](https://github.com/MithrilJS/mithril.js/pull/2649)

As a developer I tried setting up Mithril with Babel and Webpack but failed because of a variety of errors.
#### [[docs] route redirection using the history API (@pygy)](https://github.com/MithrilJS/mithril.js/pull/1767)

This is an attempt at fixing #1759, but there may be more to be added.  Feedback welcome.  ping @dontwork.
#### [Bump path-parse from 1.0.6 to 1.0.7 (@dependabot[bot])](https://github.com/MithrilJS/mithril.js/pull/2718)

Bumps [path-parse](https://github.com/jbgutierrez/path-parse) from 1.0.6 to 1.0.7.  Commits.  See full diff in compare view.
#### [Bump glob-parent from 5.1.0 to 5.1.2 (@dependabot[bot])](https://github.com/MithrilJS/mithril.js/pull/2693)

Bumps [glob-parent](https://github.com/gulpjs/glob-parent) from 5.1.0 to 5.1.2.  Release notes.  Sourced from glob-parent's releases.  v5.1.2.  Bug Fixes.
#### [Bump ajv from 6.10.2 to 6.12.6 (@dependabot[bot])](https://github.com/MithrilJS/mithril.js/pull/2741)

Bumps [ajv](https://github.com/ajv-validator/ajv) from 6.10.2 to 6.12.6.  Release notes.  Sourced from ajv's releases.  v6.12.6.  Fix performance issue of "url" format.
#### [Update standalone usage (@ghost)](https://github.com/MithrilJS/mithril.js/pull/2651)


#### [Avoid double encoding of function signatures - fixes #2720 (@StephanHoyer)](https://github.com/MithrilJS/mithril.js/pull/2721)


#### [Show previous versions (@mike-ward)](https://github.com/MithrilJS/mithril.js/pull/2353)

Add Dropdown that shows links to archived versions of the documentation.
#### [docs: improve m.request return value description (@GAumala)](https://github.com/MithrilJS/mithril.js/pull/2206)

In the m.request return value description, add a line informing that error status codes cause the promise to reject.
#### [A note on JSX events (@pereriksson)](https://github.com/MithrilJS/mithril.js/pull/2648)

Naming JSX events according to their documentation produces unexpected results with incorrectly named events when using JSX with Mithril.
#### [Document route resolution cancellation, fixes #1759 (@barneycarroll)](https://github.com/MithrilJS/mithril.js/pull/2672)

Also fixes a broken internal link.
#### [Bump marked from 0.7.0 to 4.0.10 (@dependabot[bot])](https://github.com/MithrilJS/mithril.js/pull/2717)

Bumps [marked](https://github.com/markedjs/marked) from 0.7.0 to 4.0.10.  Release notes.  Sourced from marked's releases.  v4.0.10.  4.0.10 (2022-01-13).  Bug Fixes.
#### [Flems in docs (#2348) [skip ci] (@porsager)](https://github.com/MithrilJS/mithril.js/pull/2348)

Added flems instead of the current codepen samples.
#### [Remove old TOC link (@ArthurClemens)](https://github.com/MithrilJS/mithril.js/pull/2698)

Content was moved some time ago and linked section no longer exists.
#### [Cavemansspa patch 1 (@cavemansspa)](https://github.com/MithrilJS/mithril.js/pull/2696)

Documentation update.
#### [Bump hosted-git-info from 2.8.4 to 2.8.9 (@dependabot[bot])](https://github.com/MithrilJS/mithril.js/pull/2684)

Bumps [hosted-git-info](https://github.com/npm/hosted-git-info) from 2.8.4 to 2.8.9.  Changelog.  Sourced from hosted-git-info's changelog.  2.8.9 (2021-04-07).
#### [Bump lodash from 4.17.20 to 4.17.21 (@dependabot[bot])](https://github.com/MithrilJS/mithril.js/pull/2680)

Bumps [lodash](https://github.com/lodash/lodash) from 4.17.20 to 4.17.21.  Commits.  f299b52 Bump to v4.17.21.
#### [Bump handlebars from 4.7.6 to 4.7.7 (@dependabot[bot])](https://github.com/MithrilJS/mithril.js/pull/2679)

Bumps [handlebars](https://github.com/wycats/handlebars.js) from 4.7.6 to 4.7.7.  Changelog.  Sourced from handlebars's changelog.  v4.7.7 - February 15th, 2021.
#### [Remove unreachable keyed node logic, fixes #2597 (@barneycarroll)](https://github.com/MithrilJS/mithril.js/pull/2673)


#### [Delete test-utils/README.md (@dead-claudia)](https://github.com/MithrilJS/mithril.js/pull/2674)

We don't expose this publicly anymore, so there's literally no justification for this file's existence.
#### [simple-application.md: consistent use of type=submit (@danbst)](https://github.com/MithrilJS/mithril.js/pull/2657)

When following tutorial and typing everything in, I was confused that Save button didn't work.
#### [Fix inconsistent capitalizations of "JavaScript" (@mtsknn)](https://github.com/MithrilJS/mithril.js/pull/2639)

"Javascript"/"javascript" → "JavaScript".  Fixes #2398, or at least I can't find any more incorrect capitalizations.
#### [fix some typos (@osban)](https://github.com/MithrilJS/mithril.js/pull/2487)

Found some typos.  Mainly unescaped `|` in tables, but also a few other irregularities.  Not all problems are visible in the website docs.
#### [Replace mocha by ospec in testing page (@gamtiq)](https://github.com/MithrilJS/mithril.js/pull/2585)

Fixed a typo in testing doc page.  Currently there is reference to `mocha` in the page whereas `opsec` is used.
#### [Bump acorn from 7.1.0 to 7.4.0 (@dependabot[bot])](https://github.com/MithrilJS/mithril.js/pull/2630)

Bumps [acorn](https://github.com/acornjs/acorn) from 7.1.0 to 7.4.0.  Commits.  54efb62 Mark version 7.4.0.
#### [Bump handlebars from 4.4.2 to 4.7.6 (@dependabot[bot])](https://github.com/MithrilJS/mithril.js/pull/2629)

Bumps [handlebars](https://github.com/wycats/handlebars.js) from 4.4.2 to 4.7.6.  Changelog.  Sourced from handlebars's changelog.  v4.7.6 - April 3rd, 2020.
#### [Bump lodash from 4.17.15 to 4.17.20 (@dependabot[bot])](https://github.com/MithrilJS/mithril.js/pull/2628)

Bumps [lodash](https://github.com/lodash/lodash) from 4.17.15 to 4.17.20.  Commits.  ded9bc6 Bump to v4.17.20.  63150ef Documentation fixes.
#### [Bump minimist from 1.2.0 to 1.2.3 (@dependabot[bot])](https://github.com/MithrilJS/mithril.js/pull/2627)

Bumps [minimist](https://github.com/substack/minimist) from 1.2.0 to 1.2.3.  Commits.  6457d74 1.2.3.  38a4d1c even more aggressive checks for protocol pollution.
#### [Update installation.md (@purefan)](https://github.com/MithrilJS/mithril.js/pull/2608)

Offer to install mithril as a webpack plugin.  Just makes my life easier by not having to include mithril in every one of my js files.
#### [replace slave label with replica (@stephanos)](https://github.com/MithrilJS/mithril.js/pull/2605)

One of the example is using the antiquated word "slave" for a database replica.  I updated the language and tested the change.
#### [ES6 and m.trust docs patch (@kczx3)](https://github.com/MithrilJS/mithril.js/pull/2593)

While reading through some of the documentation I saw some issues with both the ES6 and `m.trust` pages.
#### [docs: Fix simple typo, subsequece -> subsequence (@timgates42)](https://github.com/MithrilJS/mithril.js/pull/2582)

There is a small typo in mithril.js, render/render.js.  Should read `subsequence` rather than `subsequece`.
#### [change link to go to ospec instead of mocha (@akessner)](https://github.com/MithrilJS/mithril.js/pull/2576)

Change the link to point to ospec docs in github.  ospec link went to mochajs.  [issue 2575](https://github.com/MithrilJS/mithril.js/issues/2575).  N/A.  N/A.  N/A.
#### [updated to the Vimeo showcase (@CreaturesInUnitards)](https://github.com/MithrilJS/mithril.js/pull/2573)

The scrimba version of Mithril 0-60 was built on their beta platform, and doesn't really even work anymore.
#### [adding more community examples (@boazblake)](https://github.com/MithrilJS/mithril.js/pull/2567)


#### [Exclude archive of previous docs (@cztomsik)](https://github.com/MithrilJS/mithril.js/pull/2561)

update .npmignore so that archives are not included in the resulting package.  space/bandwidth savings.  fix #2552.
#### [Pimp the docs linter (and assorted changes) (@pygy)](https://github.com/MithrilJS/mithril.js/pull/2553)

Add an optional cache for faster runs.  Add a final report.  Don't return anything from `exec()`.  Cover more files.  Look for a "--cache" option.
#### [Recast key docs to be much clearer and more accurate (@dead-claudia)](https://github.com/MithrilJS/mithril.js/pull/2540)

Recast key docs to be much clearer and more accurate, including a few Flems examples to help intuitively explain things.
#### [Add `m.censor`, adjust `m.route.Link` to use it (@dead-claudia)](https://github.com/MithrilJS/mithril.js/pull/2538)

Add `m.censor`.  Adjust `m.route.Link` to use it.  Restructure a few things for better code reuse.  Fixes #2472.
#### [Update fetch() browser support in docs (@qgustavor)](https://github.com/MithrilJS/mithril.js/pull/2522)

As [Can I use](https://caniuse.com/#feat=fetch) shows `fetch()` supported since Safari 10.1 and iOS Safari 10.3.
#### [docs: Add release dates to all change-log files (@maranomynet)](https://github.com/MithrilJS/mithril.js/pull/2513)

I'd like to introduce release dates to the change log files.  Release dates are human-friendly and add a bit of historical perspective to change-log files.

# Release v2.1.0

### Minor Changes

#### [m.censor: work around a bunder bug (@kfule)](https://github.com/MithrilJS/mithril.js/pull/2752)

The internal bundler sometimes mangles the words in RegExp literals incorrectly.  Please see below.
#### [Warn about reusing mutated attrs object - fixes #2719 (@StephanHoyer)](https://github.com/MithrilJS/mithril.js/pull/2722)


#### [Send URLSearchParams as request body without extra configuration (@Coteh)](https://github.com/MithrilJS/mithril.js/pull/2695)

This PR fixes an oddity I noticed in the way `m.request` handles `URLSearchParams` object.  It now handles it in the same sort of way XHR and Fetch do it.
#### [Add `params:` to `m.route.Link`, fix docs (@dead-claudia)](https://github.com/MithrilJS/mithril.js/pull/2537)

Add `params:` to `m.route.Link`.  Minor fix to docs to reflect reality with `m.route.Link`'s `disabled:` attribute.
#### [Allow Mithril to be loaded in non-browser environments without modification (@dead-claudia)](https://github.com/MithrilJS/mithril.js/pull/2633)

Recast the global reads to all be guarded with `typeof`, so that if they aren't defined, they're just `null`.
#### [Add a `m.Fragment = "["` utility for JSX users. (@dead-claudia)](https://github.com/MithrilJS/mithril.js/pull/2744)

The title says it all, and the diff's obvious.  Resolves https://github.com/MithrilJS/mithril.js/issues/2640 and probably others.
   
### Patch Changes

#### [Enable --minimize-semver-change for pr-release (@JAForbes)](https://github.com/MithrilJS/mithril.js/pull/2769)

Minimizes semver changes on release to the minimum required version bump to satisfy major/minor/patch semver ranges.  Minimizes the semver change so that.
#### [Clean up m.route.Link (@barneycarroll)](https://github.com/MithrilJS/mithril.js/pull/2768)

An attempt at better demonstrating `m.route.Link` with less text.  Fixes #2767.
#### [Runtime-deprecate ospec, change `change-log` to `changelog`, fix a few assorted bugs (@dead-claudia)](https://github.com/MithrilJS/mithril.js/pull/2578)

This PR is in two parts: 1.  Revise the build system and some of the local dev setup.  Fully split ospec from the repo, and add it as a dependency.
#### [Add meta description to docs (@StephanHoyer)](https://github.com/MithrilJS/mithril.js/pull/2316)

rework of #2149.  added a meta description parser and meta descriptions to all docs pages.  because google.  built the docs, inspected the output manually.
#### [Fixed badges, consistent naming of Mithril.js (@tbreuss)](https://github.com/MithrilJS/mithril.js/pull/2750)

use consistent naming of Mithril.js.  fix badges in README.  Fixes issue #2749.
#### [Catch malformed URI Components (@jdiderik)](https://github.com/MithrilJS/mithril.js/pull/2711)

Fix for error thrown when a value contains non-valid / malformed URI Component.  Example: test=%c5%a1%e8ZM%80%82H.  will throw "URI malformed".
#### [Correctly handle invalid escapes in routes based on 0a5ead31c9fbd7b153c521c7f9d3df7bf826ce6c (@StephanHoyer)](https://github.com/MithrilJS/mithril.js/pull/2743)

fixes #2061.  @dead-claudia I just redid your change but slightly different in order to handle a mix of wrong and right encodings properly.
#### [Standardise vnode text representation (@barneycarroll)](https://github.com/MithrilJS/mithril.js/pull/2670)

This addresses the crucial feature of #2669: text is always represented as virtual text nodes, never as a `vnode.text`.
#### [Issue 2624 no content 204 parse (@Evoke-PHP)](https://github.com/MithrilJS/mithril.js/pull/2641)

Added guard so that JSON.parse does not fail on IE11 with no content empty string being parsed.  Fixes https://github.com/MithrilJS/mithril.js/issues/2624.
#### [[m.request] work around a bundler bug, fix #2647 (@pygy)](https://github.com/MithrilJS/mithril.js/pull/2655)

The bundler mangles identifier-like strings within RegExps, this works around the problem by not using such RegExps.
#### [Reject request on XHR timeout (@kevinfiol)](https://github.com/MithrilJS/mithril.js/pull/2646)

Derived from PR #2581.  Allows requests to properly reject on event of a timeout.
#### [Remove extra isLifecycleMethod call from removeAttr (@ZeikJT)](https://github.com/MithrilJS/mithril.js/pull/2594)

Removing an extra isLifecycleMethod in the removeAttr method, it isn't needed since it's already checked on the previous line.
#### [Fix #2601 (@dead-claudia)](https://github.com/MithrilJS/mithril.js/pull/2603)

Fix issue where ending a stream in the middle of a stream callback would result in erroneous parent stream state for the rest of that emit.  Fixes #2601.
#### [Add streams to releases again, include minified bundle, drop internal stuff from npm (@dead-claudia)](https://github.com/MithrilJS/mithril.js/pull/2539)

Add `stream/stream.js` to releases again.  Add `stream/stream.min.js` now that the process is remotely sane now.
#### [Make errors and their messages more accurate and helpful (@dead-claudia)](https://github.com/MithrilJS/mithril.js/pull/2536)

I updated error messages to be much more helpful.
#### [Fix assertion descriptions (@soulofmischief)](https://github.com/MithrilJS/mithril.js/pull/2405)

I moved the return statement to the end of define() so that it returns even if the comparison fails.
#### [Fix branch target (@dead-claudia)](https://github.com/MithrilJS/mithril.js/pull/2765)

https://github.com/MithrilJS/mithril.js/runs/6199543939?check_suite_focus=true.
#### [Automate mithril's release workflow (@JAForbes)](https://github.com/MithrilJS/mithril.js/pull/2760)

Automated releases, pre-releases, (code) rollbacks and recovery, npm publishing, change log management just by using normal github flow.
#### [rework jsx docs (@StephanHoyer)](https://github.com/MithrilJS/mithril.js/pull/2758)


#### [Add Simple Application Flems Supporting v2.0.4 and up (@tbreuss)](https://github.com/MithrilJS/mithril.js/pull/2751)

Added Flems for Simple Application supporting v2.0.4 of Mithril.js.  Fixes Issue #2710.
#### [Make example work with webpack v5.69.1 (@StephanHoyer)](https://github.com/MithrilJS/mithril.js/pull/2757)

fixes #2634.
#### [2604: correct and move text about statements in view method (@kevinfiol)](https://github.com/MithrilJS/mithril.js/pull/2748)

Addresses #2604.
#### [Fix lint errors (@StephanHoyer)](https://github.com/MithrilJS/mithril.js/pull/2745)


#### [WIP: Update modularisation details in Installation docs (@orbitbot)](https://github.com/MithrilJS/mithril.js/pull/2620)

added link to flems.io as an easier way to just try out the framework.  -.  Documentation has grown a bit stale.
#### [Added power support for the travis.yml file with ppc64le (@sreekanth370)](https://github.com/MithrilJS/mithril.js/pull/2644)

Added power support for the travis.yml file with ppc64le.  This is part of the Ubuntu distribution for ppc64le.
#### [Updated babel/webpack docs to work with latest versions (@pereriksson)](https://github.com/MithrilJS/mithril.js/pull/2649)

As a developer I tried setting up Mithril with Babel and Webpack but failed because of a variety of errors.
#### [[docs] route redirection using the history API (@pygy)](https://github.com/MithrilJS/mithril.js/pull/1767)

This is an attempt at fixing #1759, but there may be more to be added.  Feedback welcome.  ping @dontwork.
#### [Bump path-parse from 1.0.6 to 1.0.7 (@dependabot[bot])](https://github.com/MithrilJS/mithril.js/pull/2718)

Bumps [path-parse](https://github.com/jbgutierrez/path-parse) from 1.0.6 to 1.0.7.  Commits.  See full diff in compare view.
#### [Bump glob-parent from 5.1.0 to 5.1.2 (@dependabot[bot])](https://github.com/MithrilJS/mithril.js/pull/2693)

Bumps [glob-parent](https://github.com/gulpjs/glob-parent) from 5.1.0 to 5.1.2.  Release notes.  Sourced from glob-parent's releases.  v5.1.2.  Bug Fixes.
#### [Bump ajv from 6.10.2 to 6.12.6 (@dependabot[bot])](https://github.com/MithrilJS/mithril.js/pull/2741)

Bumps [ajv](https://github.com/ajv-validator/ajv) from 6.10.2 to 6.12.6.  Release notes.  Sourced from ajv's releases.  v6.12.6.  Fix performance issue of "url" format.
#### [Update standalone usage (@ghost)](https://github.com/MithrilJS/mithril.js/pull/2651)


#### [Avoid double encoding of function signatures - fixes #2720 (@StephanHoyer)](https://github.com/MithrilJS/mithril.js/pull/2721)


#### [Show previous versions (@mike-ward)](https://github.com/MithrilJS/mithril.js/pull/2353)

Add Dropdown that shows links to archived versions of the documentation.
#### [docs: improve m.request return value description (@GAumala)](https://github.com/MithrilJS/mithril.js/pull/2206)

In the m.request return value description, add a line informing that error status codes cause the promise to reject.
#### [A note on JSX events (@pereriksson)](https://github.com/MithrilJS/mithril.js/pull/2648)

Naming JSX events according to their documentation produces unexpected results with incorrectly named events when using JSX with Mithril.
#### [Document route resolution cancellation, fixes #1759 (@barneycarroll)](https://github.com/MithrilJS/mithril.js/pull/2672)

Also fixes a broken internal link.
#### [Bump marked from 0.7.0 to 4.0.10 (@dependabot[bot])](https://github.com/MithrilJS/mithril.js/pull/2717)

Bumps [marked](https://github.com/markedjs/marked) from 0.7.0 to 4.0.10.  Release notes.  Sourced from marked's releases.  v4.0.10.  4.0.10 (2022-01-13).  Bug Fixes.
#### [Flems in docs (#2348) [skip ci] (@porsager)](https://github.com/MithrilJS/mithril.js/pull/2348)

Added flems instead of the current codepen samples.
#### [Remove old TOC link (@ArthurClemens)](https://github.com/MithrilJS/mithril.js/pull/2698)

Content was moved some time ago and linked section no longer exists.
#### [Cavemansspa patch 1 (@cavemansspa)](https://github.com/MithrilJS/mithril.js/pull/2696)

Documentation update.
#### [Bump hosted-git-info from 2.8.4 to 2.8.9 (@dependabot[bot])](https://github.com/MithrilJS/mithril.js/pull/2684)

Bumps [hosted-git-info](https://github.com/npm/hosted-git-info) from 2.8.4 to 2.8.9.  Changelog.  Sourced from hosted-git-info's changelog.  2.8.9 (2021-04-07).
#### [Bump lodash from 4.17.20 to 4.17.21 (@dependabot[bot])](https://github.com/MithrilJS/mithril.js/pull/2680)

Bumps [lodash](https://github.com/lodash/lodash) from 4.17.20 to 4.17.21.  Commits.  f299b52 Bump to v4.17.21.
#### [Bump handlebars from 4.7.6 to 4.7.7 (@dependabot[bot])](https://github.com/MithrilJS/mithril.js/pull/2679)

Bumps [handlebars](https://github.com/wycats/handlebars.js) from 4.7.6 to 4.7.7.  Changelog.  Sourced from handlebars's changelog.  v4.7.7 - February 15th, 2021.
#### [Remove unreachable keyed node logic, fixes #2597 (@barneycarroll)](https://github.com/MithrilJS/mithril.js/pull/2673)


#### [Delete test-utils/README.md (@dead-claudia)](https://github.com/MithrilJS/mithril.js/pull/2674)

We don't expose this publicly anymore, so there's literally no justification for this file's existence.
#### [simple-application.md: consistent use of type=submit (@danbst)](https://github.com/MithrilJS/mithril.js/pull/2657)

When following tutorial and typing everything in, I was confused that Save button didn't work.
#### [Fix inconsistent capitalizations of "JavaScript" (@mtsknn)](https://github.com/MithrilJS/mithril.js/pull/2639)

"Javascript"/"javascript" → "JavaScript".  Fixes #2398, or at least I can't find any more incorrect capitalizations.
#### [fix some typos (@osban)](https://github.com/MithrilJS/mithril.js/pull/2487)

Found some typos.  Mainly unescaped `|` in tables, but also a few other irregularities.  Not all problems are visible in the website docs.
#### [Replace mocha by ospec in testing page (@gamtiq)](https://github.com/MithrilJS/mithril.js/pull/2585)

Fixed a typo in testing doc page.  Currently there is reference to `mocha` in the page whereas `opsec` is used.
#### [Bump acorn from 7.1.0 to 7.4.0 (@dependabot[bot])](https://github.com/MithrilJS/mithril.js/pull/2630)

Bumps [acorn](https://github.com/acornjs/acorn) from 7.1.0 to 7.4.0.  Commits.  54efb62 Mark version 7.4.0.
#### [Bump handlebars from 4.4.2 to 4.7.6 (@dependabot[bot])](https://github.com/MithrilJS/mithril.js/pull/2629)

Bumps [handlebars](https://github.com/wycats/handlebars.js) from 4.4.2 to 4.7.6.  Changelog.  Sourced from handlebars's changelog.  v4.7.6 - April 3rd, 2020.
#### [Bump lodash from 4.17.15 to 4.17.20 (@dependabot[bot])](https://github.com/MithrilJS/mithril.js/pull/2628)

Bumps [lodash](https://github.com/lodash/lodash) from 4.17.15 to 4.17.20.  Commits.  ded9bc6 Bump to v4.17.20.  63150ef Documentation fixes.
#### [Bump minimist from 1.2.0 to 1.2.3 (@dependabot[bot])](https://github.com/MithrilJS/mithril.js/pull/2627)

Bumps [minimist](https://github.com/substack/minimist) from 1.2.0 to 1.2.3.  Commits.  6457d74 1.2.3.  38a4d1c even more aggressive checks for protocol pollution.
#### [Update installation.md (@purefan)](https://github.com/MithrilJS/mithril.js/pull/2608)

Offer to install mithril as a webpack plugin.  Just makes my life easier by not having to include mithril in every one of my js files.
#### [replace slave label with replica (@stephanos)](https://github.com/MithrilJS/mithril.js/pull/2605)

One of the example is using the antiquated word "slave" for a database replica.  I updated the language and tested the change.
#### [ES6 and m.trust docs patch (@kczx3)](https://github.com/MithrilJS/mithril.js/pull/2593)

While reading through some of the documentation I saw some issues with both the ES6 and `m.trust` pages.
#### [docs: Fix simple typo, subsequece -> subsequence (@timgates42)](https://github.com/MithrilJS/mithril.js/pull/2582)

There is a small typo in mithril.js, render/render.js.  Should read `subsequence` rather than `subsequece`.
#### [change link to go to ospec instead of mocha (@akessner)](https://github.com/MithrilJS/mithril.js/pull/2576)

Change the link to point to ospec docs in github.  ospec link went to mochajs.  [issue 2575](https://github.com/MithrilJS/mithril.js/issues/2575).  N/A.  N/A.  N/A.
#### [updated to the Vimeo showcase (@CreaturesInUnitards)](https://github.com/MithrilJS/mithril.js/pull/2573)

The scrimba version of Mithril 0-60 was built on their beta platform, and doesn't really even work anymore.
#### [adding more community examples (@boazblake)](https://github.com/MithrilJS/mithril.js/pull/2567)


#### [Exclude archive of previous docs (@cztomsik)](https://github.com/MithrilJS/mithril.js/pull/2561)

update .npmignore so that archives are not included in the resulting package.  space/bandwidth savings.  fix #2552.
#### [Pimp the docs linter (and assorted changes) (@pygy)](https://github.com/MithrilJS/mithril.js/pull/2553)

Add an optional cache for faster runs.  Add a final report.  Don't return anything from `exec()`.  Cover more files.  Look for a "--cache" option.
#### [Recast key docs to be much clearer and more accurate (@dead-claudia)](https://github.com/MithrilJS/mithril.js/pull/2540)

Recast key docs to be much clearer and more accurate, including a few Flems examples to help intuitively explain things.
#### [Add `m.censor`, adjust `m.route.Link` to use it (@dead-claudia)](https://github.com/MithrilJS/mithril.js/pull/2538)

Add `m.censor`.  Adjust `m.route.Link` to use it.  Restructure a few things for better code reuse.  Fixes #2472.
#### [Update fetch() browser support in docs (@qgustavor)](https://github.com/MithrilJS/mithril.js/pull/2522)

As [Can I use](https://caniuse.com/#feat=fetch) shows `fetch()` supported since Safari 10.1 and iOS Safari 10.3.
#### [docs: Add release dates to all change-log files (@maranomynet)](https://github.com/MithrilJS/mithril.js/pull/2513)

I'd like to introduce release dates to the change log files.  Release dates are human-friendly and add a bit of historical perspective to change-log files.


================================================
FILE: docs/releasing.md
================================================
<!--meta-description
Describes how we do releases of Mithril.js
-->

# Mithril.js Release Processes

Mithril.js' release process is automated by [pr-release].  pr-release is maintained by a long time Mithril.js community member [@JAForbes](https://github.com/JAForbes).

pr-release handles the following:

- Generating changelog entries
- Automating the semver version
- Publishing releases and pre-releases to npm
- Creating github releases
- Rollbacks

## For contributors

Contributors should create their feature branch targetting the default branch `main`.  When this branch is merged `pr-release` will either generate or update a release PR from `main` to `release`.

The description and title will be managed by [pr-release], including the semver version.

Contributors who have permissions should add the correct semver label to their PR (`major` | `minor` | `patch`).  If no label is set, `patch` is assumed.

If you do not have permissions, the maintainer will set the label on your behalf.

## Changelog

Currently, `docs/recent-changes.md` holds an automatically prepended log of changes, managed by pr-release. Ideally, I want to get rid of this and just have pr-release somehow push to https://github.com/MithrilJS/docs automatically, but that may take some work.

## For maintainers

Whenever a new feature branch is opened, a reviewing maintainer should add the correct semver label to their PR (`major` | `minor` | `patch`).  If no label is set, `patch` is assumed.

If a `major` or `minor` feature branch is merged but no labels were set, you can still go back and edit the semver labels.  On label change the release pr will automatically be regenerated and will recalculate the semver version.

[pr-release]: https://pr-release.org/


================================================
FILE: hyperscript.js
================================================
"use strict"

var hyperscript = require("./render/hyperscript")

hyperscript.trust = require("./render/trust")
hyperscript.fragment = require("./render/fragment")

module.exports = hyperscript


================================================
FILE: index.js
================================================
"use strict"

var hyperscript = require("./hyperscript")
var mountRedraw = require("./mount-redraw")
var request = require("./request")
var router = require("./route")

var m = function m() { return hyperscript.apply(this, arguments) }
m.m = hyperscript
m.trust = hyperscript.trust
m.fragment = hyperscript.fragment
m.Fragment = "["
m.mount = mountRedraw.mount
m.route = router
m.render = require("./render")
m.redraw = mountRedraw.redraw
m.request = request.request
m.parseQueryString = require("./querystring/parse")
m.buildQueryString = require("./querystring/build")
m.parsePathname = require("./pathname/parse")
m.buildPathname = require("./pathname/build")
m.vnode = require("./render/vnode")
m.censor = require("./util/censor")
m.domFor = require("./render/domFor")

module.exports = m


================================================
FILE: mithril.js
================================================
;(function() {
"use strict"
function Vnode(tag, key, attrs0, children, text, dom) {
	return {tag: tag, key: key, attrs: attrs0, children: children, text: text, dom: dom, is: undefined, domSize: undefined, state: undefined, events: undefined, instance: undefined}
}
Vnode.normalize = function(node) {
	if (Array.isArray(node)) return Vnode("[", undefined, undefined, Vnode.normalizeChildren(node), undefined, undefined)
	if (node == null || typeof node === "boolean") return null
	if (typeof node === "object") return node
	return Vnode("#", undefined, undefined, String(node), undefined, undefined)
}
Vnode.normalizeChildren = function(input) {
	// Preallocate the array length (initially holey) and fill every index immediately in order.
	// Benchmarking shows better performance on V8.
	var children = new Array(input.length)
	// Count the number of keyed normalized vnodes for consistency check.
	// Note: this is a perf-sensitive check.
	// Fun fact: merging the loop like this is somehow faster than splitting
	// the check within updateNodes(), noticeably so.
	var numKeyed = 0
	for (var i = 0; i < input.length; i++) {
		children[i] = Vnode.normalize(input[i])
		if (children[i] !== null && children[i].key != null) numKeyed++
	}
	if (numKeyed !== 0 && numKeyed !== input.length) {
		throw new TypeError(children.includes(null)
			? "In fragments, vnodes must either all have keys or none have keys. You may wish to consider using an explicit keyed empty fragment, m.fragment({key: ...}), instead of a hole."
			: "In fragments, vnodes must either all have keys or none have keys."
		)
	}
	return children
}
// Note: the processing of variadic parameters is perf-sensitive.
//
// In native ES6, it might be preferable to define hyperscript and fragment
// factories with a final ...args parameter and call hyperscriptVnode(...args),
// since modern engines can optimize spread calls.
//
// However, benchmarks showed this was not faster. As a result, spread is used
// only in the parameter lists of hyperscript and fragment, while an array is
// passed to hyperscriptVnode.
var hyperscriptVnode = function(attrs1, children0) {
	if (attrs1 == null || typeof attrs1 === "object" && attrs1.tag == null && !Array.isArray(attrs1)) {
		if (children0.length === 1 && Array.isArray(children0[0])) children0 = children0[0]
	} else {
		children0 = children0.length === 0 && Array.isArray(attrs1) ? attrs1 : [attrs1, ...children0]
		attrs1 = undefined
	}
	return Vnode("", attrs1 && attrs1.key, attrs1, children0)
}
// This exists so I'm only saving it once.
var hasOwn = {}.hasOwnProperty
// This is an attrs object that is used by default when attrs is undefined or null.
var emptyAttrs = {}
// This Map manages the following:
// - Whether an attrs is cached attrs generated by compileSelector().
// - Whether the cached attrs is "static", i.e., does not contain any form attributes.
// These information will be useful to skip updating attrs in render().
//
// Since the attrs used as keys in this map are not released from the selectorCache object,
// there is no risk of memory leaks. Therefore, Map is used here instead of WeakMap.
var cachedAttrsIsStaticMap = new Map([[emptyAttrs, true]])
var selectorParser = /(?:(^|#|\.)([^#\.\[\]]+))|(\[(.+?)(?:\s*=\s*("|'|)((?:\\["'\]]|.)*?)\5)?\])/g
var selectorCache = Object.create(null)
function isEmpty(object) {
	for (var key in object) if (hasOwn.call(object, key)) return false
	return true
}
function isFormAttributeKey(key) {
	return key === "value" || key === "checked" || key === "selectedIndex" || key === "selected"
}
function compileSelector(selector) {
	var match, tag = "div", classes = [], attrs = {}, isStatic = true
	while (match = selectorParser.exec(selector)) {
		var type = match[1], value = match[2]
		if (type === "" && value !== "") tag = value
		else if (type === "#") attrs.id = value
		else if (type === ".") classes.push(value)
		else if (match[3][0] === "[") {
			var attrValue = match[6]
			if (attrValue) attrValue = attrValue.replace(/\\(["'])/g, "$1").replace(/\\\\/g, "\\")
			if (match[4] === "class") classes.push(attrValue)
			else {
				attrs[match[4]] = attrValue === "" ? attrValue : attrValue || true
				if (isFormAttributeKey(match[4])) isStatic = false
			}
		}
	}
	if (classes.length > 0) attrs.className = classes.join(" ")
	if (isEmpty(attrs)) attrs = emptyAttrs
	else cachedAttrsIsStaticMap.set(attrs, isStatic)
	return selectorCache[selector] = {tag: tag, attrs: attrs, is: attrs.is}
}
function execSelector(state, vnode) {
	vnode.tag = state.tag
	var attrs = vnode.attrs
	if (attrs == null) {
		vnode.attrs = state.attrs
		vnode.is = state.is
		return vnode
	}
	if (hasOwn.call(attrs, "class")) {
		if (attrs.class != null) attrs.className = attrs.class
		attrs.class = null
	}
	if (state.attrs !== emptyAttrs) {
		var className = attrs.className
		attrs = Object.assign({}, state.attrs, attrs)
		if (state.attrs.className != null) attrs.className =
			className != null
				? String(state.attrs.className) + " " + String(className)
				: state.attrs.className
	}
	// workaround for #2622 (reorder keys in attrs to set "type" first)
	// The DOM does things to inputs based on the "type", so it needs set first.
	// See: https://github.com/MithrilJS/mithril.js/issues/2622
	if (state.tag === "input" && hasOwn.call(attrs, "type")) {
		attrs = Object.assign({type: attrs.type}, attrs)
	}
	// This reduces the complexity of the evaluation of "is" within the render function.
	vnode.is = attrs.is
	vnode.attrs = attrs
	return vnode
}
function hyperscript(selector, attrs, ...children) {
	if (selector == null || typeof selector !== "string" && typeof selector !== "function" && typeof selector.view !== "function") {
		throw Error("The selector must be either a string or a component.");
	}
	var vnode = hyperscriptVnode(attrs, children)
	if (typeof selector === "string") {
		vnode.children = Vnode.normalizeChildren(vnode.children)
		if (selector !== "[") return execSelector(selectorCache[selector] || compileSelector(selector), vnode)
	}
	if (vnode.attrs == null) vnode.attrs = {}
	vnode.tag = selector
	return vnode
}
hyperscript.trust = function(html) {
	if (html == null) html = ""
	return Vnode("<", undefined, undefined, html, undefined, undefined)
}
hyperscript.fragment = function(attrs4, ...children1) {
	var vnode2 = hyperscriptVnode(attrs4, children1)
	if (vnode2.attrs == null) vnode2.attrs = {}
	vnode2.tag = "["
	vnode2.children = Vnode.normalizeChildren(vnode2.children)
	return vnode2
}
var delayedRemoval = new WeakMap
function *domFor(vnode4) {
	// To avoid unintended mangling of the internal bundler,
	// parameter destructuring is not used here.
	var dom = vnode4.dom
	var domSize0 = vnode4.domSize
	var generation0 = delayedRemoval.get(dom)
	if (dom != null) do {
		var nextSibling = dom.nextSibling
		if (delayedRemoval.get(dom) === generation0) {
			yield dom
			domSize0--
		}
		dom = nextSibling
	}
	while (domSize0)
}
var _14 = function() {
	var nameSpace = {
		svg: "http://www.w3.org/2000/svg",
		math: "http://www.w3.org/1998/Math/MathML"
	}
	var currentRedraw
	var currentRender
	function getDocument(dom) {
		return dom.ownerDocument;
	}
	function getNameSpace(vnode3) {
		return vnode3.attrs && vnode3.attrs.xmlns || nameSpace[vnode3.tag]
	}
	//sanity check to discourage people from doing `vnode.state = ...`
	function checkState(vnode3, original) {
		if (vnode3.state !== original) throw new Error("'vnode.state' must not be modified.")
	}
	//Note: the hook is passed as the `this` argument to allow proxying the
	//arguments without requiring a full array allocation to do so. It also
	//takes advantage of the fact the current `vnode` is the first argument in
	//all lifecycle methods.
	function callHook(vnode3) {
		var original = vnode3.state
		try {
			return this.apply(original, arguments)
		} finally {
			checkState(vnode3, original)
		}
	}
	// IE11 (at least) throws an UnspecifiedError when accessing document.activeElement when
	// inside an iframe. Catch and swallow this error, and heavy-handidly return null.
	function activeElement(dom) {
		try {
			return getDocument(dom).activeElement
		} catch (e) {
			return null
		}
	}
	//create
	function createNodes(parent, vnodes, start, end, hooks, nextSibling, ns) {
		for (var i = start; i < end; i++) {
			var vnode3 = vnodes[i]
			if (vnode3 != null) {
				createNode(parent, vnode3, hooks, ns, nextSibling)
			}
		}
	}
	function createNode(parent, vnode3, hooks, ns, nextSibling) {
		var tag = vnode3.tag
		if (typeof tag === "string") {
			vnode3.state = {}
			if (vnode3.attrs != null) initLifecycle(vnode3.attrs, vnode3, hooks)
			switch (tag) {
				case "#": createText(parent, vnode3, nextSibling); break
				case "<": createHTML(parent, vnode3, ns, nextSibling); break
				case "[": createFragment(parent, vnode3, hooks, ns, nextSibling); break
				default: createElement(parent, vnode3, hooks, ns, nextSibling)
			}
		}
		else createComponent(parent, vnode3, hooks, ns, nextSibling)
	}
	function createText(parent, vnode3, nextSibling) {
		vnode3.dom = getDocument(parent).createTextNode(vnode3.children)
		insertDOM(parent, vnode3.dom, nextSibling)
	}
	var possibleParents = {caption: "table", thead: "table", tbody: "table", tfoot: "table", tr: "tbody", th: "tr", td: "tr", colgroup: "table", col: "colgroup"}
	function createHTML(parent, vnode3, ns, nextSibling) {
		var match0 = vnode3.children.match(/^\s*?<(\w+)/im) || []
		// not using the proper parent makes the child element(s) vanish.
		//     var div = document.createElement("div")
		//     div.innerHTML = "<td>i</td><td>j</td>"
		//     console.log(div.innerHTML)
		// --> "ij", no <td> in sight.
		var temp = getDocument(parent).createElement(possibleParents[match0[1]] || "div")
		if (ns === "http://www.w3.org/2000/svg") {
			temp.innerHTML = "<svg xmlns=\"http://www.w3.org/2000/svg\">" + vnode3.children + "</svg>"
			temp = temp.firstChild
		} else {
			temp.innerHTML = vnode3.children
		}
		vnode3.dom = temp.firstChild
		vnode3.domSize = temp.childNodes.length
		var fragment = getDocument(parent).createDocumentFragment()
		var child
		while (child = temp.firstChild) {
			fragment.appendChild(child)
		}
		insertDOM(parent, fragment, nextSibling)
	}
	function createFragment(parent, vnode3, hooks, ns, nextSibling) {
		var fragment = getDocument(parent).createDocumentFragment()
		if (vnode3.children != null) {
			var children2 = vnode3.children
			createNodes(fragment, children2, 0, children2.length, hooks, null, ns)
		}
		vnode3.dom = fragment.firstChild
		vnode3.domSize = fragment.childNodes.length
		insertDOM(parent, fragment, nextSibling)
	}
	function createElement(parent, vnode3, hooks, ns, nextSibling) {
		var tag = vnode3.tag
		var attrs5 = vnode3.attrs
		var is = vnode3.is
		ns = getNameSpace(vnode3) || ns
		var element = ns ?
			is ? getDocument(parent).createElementNS(ns, tag, {is: is}) : getDocument(parent).createElementNS(ns, tag) :
			is ? getDocument(parent).createElement(tag, {is: is}) : getDocument(parent).createElement(tag)
		vnode3.dom = element
		if (attrs5 != null) {
			setAttrs(vnode3, attrs5, ns)
		}
		insertDOM(parent, element, nextSibling)
		if (!maybeSetContentEditable(vnode3)) {
			if (vnode3.children != null) {
				var children2 = vnode3.children
				createNodes(element, children2, 0, children2.length, hooks, null, ns)
				if (vnode3.tag === "select" && attrs5 != null) setLateSelectAttrs(vnode3, attrs5)
			}
		}
	}
	function initComponent(vnode3, hooks) {
		var sentinel
		if (typeof vnode3.tag.view === "function") {
			vnode3.state = Object.create(vnode3.tag)
			sentinel = vnode3.state.view
			if (sentinel.$$reentrantLock$$ != null) return
			sentinel.$$reentrantLock$$ = true
		} else {
			vnode3.state = void 0
			sentinel = vnode3.tag
			if (sentinel.$$reentrantLock$$ != null) return
			sentinel.$$reentrantLock$$ = true
			vnode3.state = (vnode3.tag.prototype != null && typeof vnode3.tag.prototype.view === "function") ? new vnode3.tag(vnode3) : vnode3.tag(vnode3)
		}
		initLifecycle(vnode3.state, vnode3, hooks)
		if (vnode3.attrs != null) initLifecycle(vnode3.attrs, vnode3, hooks)
		vnode3.instance = Vnode.normalize(callHook.call(vnode3.state.view, vnode3))
		if (vnode3.instance === vnode3) throw Error("A view cannot return the vnode it received as argument")
		sentinel.$$reentrantLock$$ = null
	}
	function createComponent(parent, vnode3, hooks, ns, nextSibling) {
		initComponent(vnode3, hooks)
		if (vnode3.instance != null) {
			createNode(parent, vnode3.instance, hooks, ns, nextSibling)
			vnode3.dom = vnode3.instance.dom
			vnode3.domSize = vnode3.instance.domSize
		}
		else {
			vnode3.domSize = 0
		}
	}
	//update
	/**
	 * @param {Element|Fragment} parent - the parent element
	 * @param {Vnode[] | null} old - the list of vnodes of the last `render()` call for
	 *                               this part of the tree
	 * @param {Vnode[] | null} vnodes - as above, but for the current `render()` call.
	 * @param {Function[]} hooks - an accumulator of post-render hooks (oncreate/onupdate)
	 * @param {Element | null} nextSibling - the next DOM node if we're dealing with a
	 *                                       fragment that is not the last item in its
	 *                                       parent
	 * @param {'svg' | 'math' | String | null} ns) - the current XML namespace, if any
	 * @returns void
	 */
	// This function diffs and patches lists of vnodes, both keyed and unkeyed.
	//
	// We will:
	//
	// 1. describe its general structure
	// 2. focus on the diff algorithm optimizations
	// 3. discuss DOM node operations.
	// ## Overview:
	//
	// The updateNodes() function:
	// - deals with trivial cases
	// - determines whether the lists are keyed or unkeyed based on the first non-null node
	//   of each list.
	// - diffs them and patches the DOM if needed (that's the brunt of the code)
	// - manages the leftovers: after diffing, are there:
	//   - old nodes left to remove?
	// 	 - new nodes to insert?
	// 	 deal with them!
	//
	// The lists are only iterated over once, with an exception for the nodes in `old` that
	// are visited in the fourth part of the diff and in the `removeNodes` loop.
	// ## Diffing
	//
	// Reading https://github.com/localvoid/ivi/blob/ddc09d06abaef45248e6133f7040d00d3c6be853/packages/ivi/src/vdom/implementation.ts#L617-L837
	// may be good for context on longest increasing subsequence-based logic for moving nodes.
	//
	// In order to diff keyed lists, one has to
	//
	// 1) match nodes in both lists, per key, and update them accordingly
	// 2) create the nodes present in the new list, but absent in the old one
	// 3) remove the nodes present in the old list, but absent in the new one
	// 4) figure out what nodes in 1) to move in order to minimize the DOM operations.
	//
	// To achieve 1) one can create a dictionary of keys => index (for the old list), then iterate
	// over the new list and for each new vnode, find the corresponding vnode in the old list using
	// the map.
	// 2) is achieved in the same step: if a new node has no corresponding entry in the map, it is new
	// and must be created.
	// For the removals, we actually remove the nodes that have been updated from the old list.
	// The nodes that remain in that list after 1) and 2) have been performed can be safely removed.
	// The fourth step is a bit more complex and relies on the longest increasing subsequence (LIS)
	// algorithm.
	//
	// the longest increasing subsequence is the list of nodes that can
Download .txt
gitextract_7dh5okin/

├── .deploy.enc
├── .editorconfig
├── .eslintignore
├── .eslintrc.js
├── .gitattributes
├── .github/
│   ├── CODEOWNERS
│   ├── ISSUE_TEMPLATE/
│   │   ├── 1-core.yml
│   │   ├── 2-stream.yml
│   │   └── config.yml
│   ├── PULL_REQUEST_TEMPLATE.md
│   ├── dependabot.yml
│   └── workflows/
│       ├── issue-create.yml
│       ├── notify-release.yml
│       ├── pr-create-release.yml
│       ├── publish-prerelease.yml
│       ├── push-release.yml
│       ├── rollback.yml
│       └── test.yml
├── .gitignore
├── .npmignore
├── LICENSE
├── README.md
├── api/
│   ├── mount-redraw.js
│   ├── router.js
│   └── tests/
│       ├── test-mountRedraw.js
│       ├── test-router.js
│       └── test-routerGetSet.js
├── browser.js
├── docs/
│   ├── code-of-conduct.md
│   ├── contributing.md
│   ├── credits.md
│   ├── recent-changes.md
│   └── releasing.md
├── hyperscript.js
├── index.js
├── mithril.js
├── mount-redraw.js
├── mount.js
├── package.json
├── pathname/
│   ├── build.js
│   ├── compileTemplate.js
│   ├── parse.js
│   └── tests/
│       ├── test-buildPathname.js
│       ├── test-compileTemplate.js
│       └── test-parsePathname.js
├── performance/
│   ├── index.html
│   └── test-perf.js
├── querystring/
│   ├── build.js
│   ├── parse.js
│   └── tests/
│       ├── test-buildQueryString.js
│       └── test-parseQueryString.js
├── redraw.js
├── render/
│   ├── cachedAttrsIsStaticMap.js
│   ├── delayedRemoval.js
│   ├── domFor.js
│   ├── emptyAttrs.js
│   ├── fragment.js
│   ├── hyperscript.js
│   ├── hyperscriptVnode.js
│   ├── render.js
│   ├── tests/
│   │   ├── .eslintrc.js
│   │   ├── manual/
│   │   │   ├── case-handling.html
│   │   │   ├── iframe.html
│   │   │   ├── index.html
│   │   │   ├── minlength-input.html
│   │   │   └── minlength-textarea.html
│   │   ├── test-attributes.js
│   │   ├── test-component.js
│   │   ├── test-createElement.js
│   │   ├── test-createFragment.js
│   │   ├── test-createHTML.js
│   │   ├── test-createNodes.js
│   │   ├── test-createText.js
│   │   ├── test-domFor.js
│   │   ├── test-event.js
│   │   ├── test-fragment.js
│   │   ├── test-hyperscript.js
│   │   ├── test-input.js
│   │   ├── test-normalize.js
│   │   ├── test-normalizeChildren.js
│   │   ├── test-normalizeComponentChildren.js
│   │   ├── test-onbeforeremove.js
│   │   ├── test-onbeforeupdate.js
│   │   ├── test-oncreate.js
│   │   ├── test-oninit.js
│   │   ├── test-onremove.js
│   │   ├── test-onupdate.js
│   │   ├── test-render-hyperscript-integration.js
│   │   ├── test-render.js
│   │   ├── test-textContent.js
│   │   ├── test-trust.js
│   │   ├── test-updateElement.js
│   │   ├── test-updateFragment.js
│   │   ├── test-updateHTML.js
│   │   ├── test-updateNodes.js
│   │   ├── test-updateNodesFuzzer.js
│   │   └── test-updateText.js
│   ├── trust.js
│   └── vnode.js
├── render.js
├── request/
│   ├── request.js
│   └── tests/
│       └── test-request.js
├── request.js
├── route.js
├── scripts/
│   ├── .eslintrc.js
│   ├── _bundler-impl.js
│   ├── bundler-readme.md
│   ├── bundler.js
│   ├── minify-stream.js
│   ├── set-versioned-branch.sh
│   └── tests/
│       └── test-bundler.js
├── stream/
│   ├── stream.js
│   └── tests/
│       ├── test-scan.js
│       ├── test-scanMerge.js
│       └── test-stream.js
├── stream.js
├── test-utils/
│   ├── browserMock.js
│   ├── callAsync.js
│   ├── components.js
│   ├── domMock.js
│   ├── parseURL.js
│   ├── pushStateMock.js
│   ├── tests/
│   │   ├── test-browserMock.js
│   │   ├── test-callAsync.js
│   │   ├── test-components.js
│   │   ├── test-domMock.js
│   │   ├── test-parseURL.js
│   │   ├── test-pushStateMock.js
│   │   ├── test-throttleMock.js
│   │   └── test-xhrMock.js
│   ├── throttleMock.js
│   └── xhrMock.js
├── tests/
│   └── test-api.js
└── util/
    ├── censor.js
    ├── decodeURIComponentSafe.js
    ├── hasOwn.js
    └── tests/
        ├── test-censor.js
        └── test-decodeURIComponentSafe.js
Download .txt
SYMBOL INDEX (307 symbols across 39 files)

FILE: api/mount-redraw.js
  function sync (line 10) | function sync() {
  function redraw (line 18) | function redraw() {
  function mount (line 30) | function mount(root, component) {

FILE: api/router.js
  function resolveRoute (line 43) | function resolveRoute() {
  function fireAsync (line 112) | function fireAsync() {
  function route (line 122) | function route(root, defaultRoute, routes) {

FILE: api/tests/test-router.js
  function waitCycles (line 28) | function waitCycles(n) {
  function lock (line 50) | function lock(func) {
  function Cmp (line 372) | function Cmp() {return {view: lock(view)}}

FILE: mithril.js
  function Vnode (line 3) | function Vnode(tag, key, attrs0, children, text, dom) {
  function isEmpty (line 65) | function isEmpty(object) {
  function isFormAttributeKey (line 69) | function isFormAttributeKey(key) {
  function compileSelector (line 72) | function compileSelector(selector) {
  function execSelector (line 94) | function execSelector(state, vnode) {
  function hyperscript (line 125) | function hyperscript(selector, attrs, ...children) {
  function getDocument (line 173) | function getDocument(dom) {
  function getNameSpace (line 176) | function getNameSpace(vnode3) {
  function checkState (line 180) | function checkState(vnode3, original) {
  function callHook (line 187) | function callHook(vnode3) {
  function activeElement (line 197) | function activeElement(dom) {
  function createNodes (line 205) | function createNodes(parent, vnodes, start, end, hooks, nextSibling, ns) {
  function createNode (line 213) | function createNode(parent, vnode3, hooks, ns, nextSibling) {
  function createText (line 227) | function createText(parent, vnode3, nextSibling) {
  function createHTML (line 232) | function createHTML(parent, vnode3, ns, nextSibling) {
  function createFragment (line 255) | function createFragment(parent, vnode3, hooks, ns, nextSibling) {
  function createElement (line 265) | function createElement(parent, vnode3, hooks, ns, nextSibling) {
  function initComponent (line 286) | function initComponent(vnode3, hooks) {
  function createComponent (line 306) | function createComponent(parent, vnode3, hooks, ns, nextSibling) {
  function updateNodes (line 411) | function updateNodes(parent, old, vnodes, hooks, nextSibling, ns) {
  function updateNode (line 536) | function updateNode(parent, old, vnode3, hooks, nextSibling, ns) {
  function updateText (line 560) | function updateText(old, vnode3) {
  function updateHTML (line 566) | function updateHTML(parent, old, vnode3, ns, nextSibling) {
  function updateFragment (line 576) | function updateFragment(parent, old, vnode3, hooks, nextSibling, ns) {
  function updateElement (line 591) | function updateElement(old, vnode3, hooks, ns) {
  function updateComponent (line 601) | function updateComponent(parent, old, vnode3, hooks, nextSibling, ns) {
  function getKeyMap (line 617) | function getKeyMap(vnodes, start, end) {
  function makeLisIndices (line 634) | function makeLisIndices(a) {
  function getNextSibling (line 674) | function getNextSibling(vnodes, i, nextSibling) {
  function moveDOM (line 681) | function moveDOM(parent, vnode3, nextSibling) {
  function insertDOM (line 694) | function insertDOM(parent, dom, nextSibling) {
  function maybeSetContentEditable (line 698) | function maybeSetContentEditable(vnode3) {
  function removeNodes (line 712) | function removeNodes(parent, vnodes, start, end) {
  function tryBlockRemove (line 718) | function tryBlockRemove(parent, vnode3, source, counter) {
  function tryResumeRemove (line 730) | function tryResumeRemove(parent, vnode3, counter) {
  function removeNode (line 736) | function removeNode(parent, vnode3) {
  function removeDOM (line 742) | function removeDOM(parent, vnode3) {
  function onremove (line 750) | function onremove(vnode3) {
  function setAttrs (line 767) | function setAttrs(vnode3, attrs5, ns) {
  function setAttr (line 772) | function setAttr(vnode3, key, old, value, ns) {
  function removeAttr (line 805) | function removeAttr(vnode3, key, old, ns) {
  function setLateSelectAttrs (line 826) | function setLateSelectAttrs(vnode3, attrs5) {
  function updateAttrs (line 839) | function updateAttrs(vnode3, old, attrs5, ns) {
  function isFormAttribute (line 859) | function isFormAttribute(vnode3, attr) {
  function isLifecycleMethod (line 862) | function isLifecycleMethod(attr) {
  function hasPropertyKey (line 865) | function hasPropertyKey(vnode3, key, ns) {
  function updateStyle (line 876) | function updateStyle(element, old, style) {
  function EventDict (line 928) | function EventDict() {
  function updateEvent (line 953) | function updateEvent(vnode3, key, value) {
  function initLifecycle (line 971) | function initLifecycle(source, vnode3, hooks) {
  function updateLifecycle (line 975) | function updateLifecycle(source, vnode3, hooks) {
  function shouldNotUpdate (line 978) | function shouldNotUpdate(vnode3, old) {
  function sync (line 1039) | function sync() {
  function redraw (line 1046) | function redraw() {
  function mount (line 1056) | function mount(root, component) {
  function destructure (line 1081) | function destructure(key2, value1) {
  function PromiseProxy (line 1130) | function PromiseProxy(executor) {
  function makeRequest (line 1133) | function makeRequest(url, args) {
  function hasHeader (line 1261) | function hasHeader(args, name) {
  function complete (line 1274) | function complete() {
  function wrap (line 1278) | function wrap(promise) {
  function resolveRoute (line 1492) | function resolveRoute() {
  function fireAsync (line 1551) | function fireAsync() {
  function route (line 1560) | function route(root, defaultRoute, routes) {

FILE: pathname/tests/test-buildPathname.js
  function test (line 7) | function test(prefix) {

FILE: performance/test-perf.js
  function cycleRoot (line 58) | function cycleRoot() {
  function get (line 345) | function get(obj, i) { return obj[i % obj.length] }

FILE: querystring/build.js
  function destructure (line 13) | function destructure(key, value) {

FILE: render/hyperscript.js
  function isEmpty (line 12) | function isEmpty(object) {
  function isFormAttributeKey (line 17) | function isFormAttributeKey(key) {
  function compileSelector (line 21) | function compileSelector(selector) {
  function execSelector (line 44) | function execSelector(state, vnode) {
  function hyperscript (line 84) | function hyperscript(selector, attrs, ...children) {

FILE: render/render.js
  function getDocument (line 17) | function getDocument(dom) {
  function getNameSpace (line 21) | function getNameSpace(vnode) {
  function checkState (line 26) | function checkState(vnode, original) {
  function callHook (line 34) | function callHook(vnode) {
  function activeElement (line 45) | function activeElement(dom) {
  function createNodes (line 53) | function createNodes(parent, vnodes, start, end, hooks, nextSibling, ns) {
  function createNode (line 61) | function createNode(parent, vnode, hooks, ns, nextSibling) {
  function createText (line 75) | function createText(parent, vnode, nextSibling) {
  function createHTML (line 80) | function createHTML(parent, vnode, ns, nextSibling) {
  function createFragment (line 103) | function createFragment(parent, vnode, hooks, ns, nextSibling) {
  function createElement (line 113) | function createElement(parent, vnode, hooks, ns, nextSibling) {
  function initComponent (line 139) | function initComponent(vnode, hooks) {
  function createComponent (line 159) | function createComponent(parent, vnode, hooks, ns, nextSibling) {
  function updateNodes (line 270) | function updateNodes(parent, old, vnodes, hooks, nextSibling, ns) {
  function updateNode (line 397) | function updateNode(parent, old, vnode, hooks, nextSibling, ns) {
  function updateText (line 421) | function updateText(old, vnode) {
  function updateHTML (line 427) | function updateHTML(parent, old, vnode, ns, nextSibling) {
  function updateFragment (line 437) | function updateFragment(parent, old, vnode, hooks, nextSibling, ns) {
  function updateElement (line 452) | function updateElement(old, vnode, hooks, ns) {
  function updateComponent (line 463) | function updateComponent(parent, old, vnode, hooks, nextSibling, ns) {
  function makeLisIndices (line 485) | function makeLisIndices(a) {
  function getNextSibling (line 526) | function getNextSibling(vnodes, i, end, nextSibling) {
  function moveDOM (line 534) | function moveDOM(parent, vnode, nextSibling) {
  function insertDOM (line 548) | function insertDOM(parent, dom, nextSibling) {
  function maybeSetContentEditable (line 553) | function maybeSetContentEditable(vnode) {
  function removeNodes (line 568) | function removeNodes(parent, vnodes, start, end) {
  function tryBlockRemove (line 574) | function tryBlockRemove(parent, vnode, source, counter) {
  function tryResumeRemove (line 588) | function tryResumeRemove(parent, vnode, counter) {
  function removeNode (line 594) | function removeNode(parent, vnode) {
  function removeDOM (line 600) | function removeDOM(parent, vnode) {
  function onremove (line 609) | function onremove(vnode) {
  function setAttrs (line 627) | function setAttrs(vnode, attrs, ns) {
  function setAttr (line 632) | function setAttr(vnode, key, old, value, ns) {
  function removeAttr (line 665) | function removeAttr(vnode, key, old, ns) {
  function setLateSelectAttrs (line 686) | function setLateSelectAttrs(vnode, attrs) {
  function updateAttrs (line 699) | function updateAttrs(vnode, old, attrs, ns) {
  function isFormAttribute (line 719) | function isFormAttribute(vnode, attr) {
  function isLifecycleMethod (line 722) | function isLifecycleMethod(attr) {
  function hasPropertyKey (line 725) | function hasPropertyKey(vnode, key, ns) {
  function updateStyle (line 737) | function updateStyle(element, old, style) {
  function EventDict (line 790) | function EventDict() {
  function updateEvent (line 816) | function updateEvent(vnode, key, value) {
  function initLifecycle (line 835) | function initLifecycle(source, vnode, hooks) {
  function updateLifecycle (line 839) | function updateLifecycle(source, vnode, hooks) {
  function shouldNotUpdate (line 842) | function shouldNotUpdate(vnode, old) {

FILE: render/tests/test-attributes.js
  function makeSelect (line 520) | function makeSelect(value) {

FILE: render/tests/test-component.js
  function view (line 448) | function view() {
  function view (line 488) | function view() {
  function init (line 850) | function init(vnode) {
  function init (line 867) | function init(vnode) {
  function init (line 1125) | function init(vnode) {

FILE: render/tests/test-domFor.js
  method oncreate (line 20) | oncreate(vnode){
  method oncreate (line 30) | oncreate(vnode){
  function oncreate (line 44) | function oncreate(vnode){
  function onupdate (line 57) | function onupdate(vnode) {
  method onbeforeremove (line 75) | onbeforeremove(){return {then(){}, finally(){}}}
  method then (line 97) | then(resolve){resolve()}
  method then (line 120) | then(resolve){thenCBA = resolve}
  method then (line 123) | then(resolve){thenCBB = resolve}
  method then (line 126) | then(resolve){thenCBC = resolve}
  method then (line 246) | then(resolve){thenCBA = resolve}
  method then (line 249) | then(resolve){thenCBB = resolve}
  method then (line 252) | then(resolve){thenCBC = resolve}
  method then (line 372) | then(resolve){thenCBA = resolve}
  method then (line 375) | then(resolve){thenCBB = resolve}
  method then (line 378) | then(resolve){thenCBC = resolve}
  method then (line 498) | then(resolve){thenCBA = resolve}
  method then (line 501) | then(resolve){thenCBB = resolve}
  method then (line 504) | then(resolve){thenCBC = resolve}
  method then (line 624) | then(resolve){thenCBA = resolve}
  method then (line 627) | then(resolve){thenCBB = resolve}
  method then (line 630) | then(resolve){thenCBC = resolve}
  method then (line 750) | then(resolve){thenCBA = resolve}
  method then (line 753) | then(resolve){thenCBB = resolve}
  method then (line 756) | then(resolve){thenCBC = resolve}
  method view (line 878) | view(){return m("div")}
  method oncreate (line 879) | oncreate(vnode){
  method view (line 904) | view({children}){return children}
  method then (line 915) | then(){}
  method finally (line 915) | finally(){}
  method view (line 943) | view({children}){return children}
  method then (line 973) | then(){}
  method finally (line 973) | finally(){}
  method view (line 976) | view({children}){return children}

FILE: render/tests/test-event.js
  function eventSpy (line 21) | function eventSpy(fn) {
  method then (line 441) | then(resolve){thenCB = resolve}
  method then (line 501) | then(resolve){thenCB = resolve}
  method then (line 602) | then(_, reject){rejectCB = reject}
  method then (line 673) | then(resolve){thenCB = resolve}
  method then (line 744) | then(_, reject){rejectCB = reject}

FILE: render/tests/test-fragment.js
  function fragmentStr (line 7) | function fragmentStr() {
  function runTest (line 13) | function runTest(name, fragment) {

FILE: render/tests/test-input.js
  function makeSelect (line 261) | function makeSelect() {

FILE: render/tests/test-onbeforeremove.js
  function remove (line 45) | function remove(node) {
  function remove (line 65) | function remove(node) {
  function onbeforeremove (line 84) | function onbeforeremove(node) {

FILE: render/tests/test-onbeforeupdate.js
  function onbeforeupdate (line 59) | function onbeforeupdate(vnode, old) {
  function onbeforeupdate (line 78) | function onbeforeupdate() {
  function onbeforeupdate (line 94) | function onbeforeupdate() {
  function onbeforeupdate (line 231) | function onbeforeupdate(vnode, old) {
  function onbeforeupdate (line 257) | function onbeforeupdate() {
  function onbeforeupdate (line 280) | function onbeforeupdate() {
  method view (line 342) | view(v) {

FILE: render/tests/test-oncreate.js
  function create (line 182) | function create(vnode) {

FILE: render/tests/test-oninit.js
  function create (line 167) | function create(vnode) {

FILE: render/tests/test-onremove.js
  method view (line 240) | view({children}){return children}
  method onbeforeremove (line 241) | onbeforeremove(){
  function update (line 245) | function update(id, showParent, showChild) {

FILE: render/tests/test-onupdate.js
  function update (line 144) | function update(vnode) {

FILE: render/tests/test-render.js
  function run (line 71) | function run() {
  function init (line 74) | function init() {
  function A (line 92) | function A(){}
  function A (line 113) | function A(){}
  function A (line 134) | function A(){throw new Error("error")}
  function A (line 155) | function A() {
  function A (line 178) | function A() {
  function A (line 199) | function A() {
  function A (line 335) | function A() {

FILE: render/tests/test-updateHTML.js
  function childKeysOf (line 52) | function childKeysOf(elem, key) {

FILE: render/tests/test-updateNodes.js
  function vnodify (line 11) | function vnodify(str) {

FILE: render/tests/test-updateNodesFuzzer.js
  function longestIncreasingSubsequence (line 75) | function longestIncreasingSubsequence(a) {
  function rand (line 121) | function rand(min, max) {
  function ins (line 125) | function ins(arr, qty) {
  function del (line 132) | function del(arr, qty) {
  function mov (line 137) | function mov(arr, qty) {
  function fuzzTest (line 146) | function fuzzTest(delMax, movMax, insMax) {
  function addSpies (line 182) | function addSpies(node) {

FILE: render/vnode.js
  function Vnode (line 3) | function Vnode(tag, key, attrs, children, text, dom) {

FILE: request/request.js
  function PromiseProxy (line 7) | function PromiseProxy(executor) {
  function makeRequest (line 11) | function makeRequest(url, args) {
  function hasHeader (line 156) | function hasHeader(args, name) {
  function complete (line 170) | function complete() {
  function wrap (line 176) | function wrap(promise) {

FILE: request/tests/test-request.js
  function config (line 381) | function config(xhr) {
  function config (line 439) | function config(xhr) {
  function config (line 451) | function config(xhr) {
  function handleAbort (line 464) | function handleAbort(xhr) {
  function handleAbort (line 494) | function handleAbort(xhr) {
  function checkUnset (line 772) | function checkUnset(method) {
  function checkSet (line 796) | function checkSet(method, body) {

FILE: scripts/_bundler-impl.js
  function isFile (line 11) | function isFile(filepath) {
  function escapeRegExp (line 14) | function escapeRegExp(string) {
  function escapeReplace (line 17) | function escapeReplace(string) {
  function resolve (line 21) | async function resolve(filepath, filename) {
  function matchAll (line 51) | function matchAll(str, regexp) {
  function process (line 66) | async function process(filepath, data) {
  function exportCode (line 105) | async function exportCode(filename, filepath, def, variable, eq, rest, u...

FILE: scripts/bundler.js
  function add (line 29) | function add(value) {
  function format (line 34) | function format(n) {
  function build (line 38) | async function build() {

FILE: scripts/minify-stream.js
  function format (line 29) | function format(n) {
  function minify (line 34) | async function minify() {

FILE: scripts/tests/test-bundler.js
  function write (line 18) | async function write(filepath, data) {
  function setup (line 27) | function setup(files) {

FILE: stream/stream.js
  function Stream (line 22) | function Stream(value) {
  function combine (line 100) | function combine(fn, streams) {
  function merge (line 135) | function merge(streams) {
  function scan (line 139) | function scan(fn, acc, origin) {
  function scanMerge (line 149) | function scanMerge(tuples, seed) {
  function lift (line 167) | function lift() {
  function open (line 175) | function open(s) {

FILE: stream/tests/test-stream.js
  function f (line 642) | function f(x) {return x * 2}
  function g (line 643) | function g(x) {return x * x}

FILE: test-utils/components.js
  function res (line 16) | function res(){}

FILE: test-utils/domMock.js
  function registerSpies (line 39) | function registerSpies(element, spies) {
  function getSpies (line 50) | function getSpies(element) {
  function isModernEvent (line 55) | function isModernEvent(type) {
  function dispatchEvent (line 58) | function dispatchEvent(e) {
  function appendChild (line 82) | function appendChild(child) {
  function removeChild (line 101) | function removeChild(child) {
  function insertBefore (line 112) | function insertBefore(child, reference) {
  function getAttribute (line 142) | function getAttribute(name) {
  function setAttribute (line 146) | function setAttribute(name, value) {
  function setAttributeNS (line 165) | function setAttributeNS(ns, name, value) {
  function removeAttribute (line 169) | function removeAttribute(name) {
  function hasAttribute (line 172) | function hasAttribute(name) {
  function splitDeclList (line 185) | function splitDeclList(declList) {
  function parseMarkup (line 207) | function parseMarkup(value, root, voidElements, xmlns) {
  function DOMParser (line 231) | function DOMParser() {}
  function camelCase (line 241) | function camelCase(string) {
  method firstChild (line 324) | get firstChild() {
  method nextSibling (line 327) | get nextSibling() {
  method textContent (line 334) | set textContent(value) {
  method innerHTML (line 339) | set innerHTML(value) {
  method style (line 353) | get style() {
  method style (line 356) | set style(value){
  method className (line 359) | get className() {
  method className (line 362) | set className(value) {
  function getOptions (line 598) | function getOptions(element) {
  function getOptionValue (line 606) | function getOptionValue(element) {
  method childNodes (line 709) | get childNodes() { return [] }
  method firstChild (line 710) | get firstChild() { return null }
  method nodeValue (line 711) | get nodeValue() {return nodeValue}
  method nodeValue (line 712) | set nodeValue(value) {
  method nextSibling (line 717) | get nextSibling() {
  method firstChild (line 735) | get firstChild() {
  method activeElement (line 746) | get activeElement() {return activeElement}

FILE: test-utils/pushStateMock.js
  function debouncedAsync (line 6) | function debouncedAsync(f) {
  function getURL (line 30) | function getURL() {
  function setURL (line 34) | function setURL(value) {
  function prefix (line 51) | function prefix(prefix, value) {
  function _hashchange (line 55) | function _hashchange() {
  function popstate (line 59) | function popstate() {
  function unload (line 62) | function unload() {
  method protocol (line 67) | get protocol() {
  method hostname (line 70) | get hostname() {
  method port (line 73) | get port() {
  method pathname (line 76) | get pathname() {
  method search (line 79) | get search() {
  method hash (line 82) | get hash() {
  method origin (line 85) | get origin() {
  method host (line 89) | get host() {
  method href (line 93) | get href() {
  method protocol (line 97) | set protocol(value) {
  method hostname (line 100) | set hostname(value) {
  method port (line 106) | set port(value) {
  method pathname (line 113) | set pathname(value) {
  method search (line 120) | set search(value) {
  method hash (line 126) | set hash(value) {
  method origin (line 134) | set origin(value) {
  method host (line 137) | set host(value) {
  method href (line 140) | set href(value) {
  method state (line 182) | get state() {

FILE: test-utils/tests/test-pushStateMock.js
  function pop (line 474) | function pop(e) {

FILE: test-utils/xhrMock.js
  function FormData (line 14) | function FormData() {}
Condensed preview — 138 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,019K chars).
[
  {
    "path": ".editorconfig",
    "chars": 254,
    "preview": "root = true\n\n[*]\ncharset = utf-8\n\n[*.{js,json,yml,html,md}]\nindent_style = tab\ntab_width = 4\ntrim_trailing_whitespace = "
  },
  {
    "path": ".eslintignore",
    "chars": 182,
    "preview": "/coverage\n/node_modules\n/jsconfig.json\n/npm-debug.log\n/.vscode\n/.DS_Store\n/.eslintcache\n\n# These are artifacts from vari"
  },
  {
    "path": ".eslintrc.js",
    "chars": 5383,
    "preview": "\"use strict\"\n\nmodule.exports = {\n\t\"env\": {\n\t\t\"browser\": true,\n\t\t\"commonjs\": true,\n\t\t\"es6\": true,\n\t\t\"node\": true\n\t},\n\t\"ex"
  },
  {
    "path": ".gitattributes",
    "chars": 103,
    "preview": "* text=auto\n/mithril.js binary\n/mithril.min.js binary\n\n# Assets\n*.png binary\n*.svg binary\n*.ico binary\n"
  },
  {
    "path": ".github/CODEOWNERS",
    "chars": 52,
    "preview": "* @MithrilJS/Committers\n/.github/ @MithrilJS/Admins\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/1-core.yml",
    "chars": 4059,
    "preview": "name: '🐛 Framework Bug'\ndescription: Report a bug in Mithril.js core\nassignees: dead-claudia\nlabels:\n- 'Area: Core'\nbody"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/2-stream.yml",
    "chars": 4087,
    "preview": "name: '🌊 Mithril.js Streams bug'\ndescription: Report an issue with Mithril.js's Streams module\nassignees: dead-claudia\nl"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "chars": 1084,
    "preview": "blank_issues_enabled: true\ncontact_links:\n  - name: ℹ Questions, Ideas, and Discussions\n    url: https://github.com/Mith"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "chars": 1359,
    "preview": "<!--- Provide a general summary of your changes in the Title above -->\n\n## Description\n<!--- Describe your changes in de"
  },
  {
    "path": ".github/dependabot.yml",
    "chars": 939,
    "preview": "# To get started with Dependabot version updates, you'll need to specify which\n# package ecosystems to update and where "
  },
  {
    "path": ".github/workflows/issue-create.yml",
    "chars": 222,
    "preview": "name: Ping triage on issue create\non:\n  issues:\n    types: [opened]\n  pull_request_target:\n    types: [opened]\njobs:\n  n"
  },
  {
    "path": ".github/workflows/notify-release.yml",
    "chars": 298,
    "preview": "name: Notify release\n\non:\n  push:\n    tags: ['v*']\n\njobs:\n  update:\n    runs-on: ubuntu-latest\n    steps:\n    - name: Se"
  },
  {
    "path": ".github/workflows/pr-create-release.yml",
    "chars": 377,
    "preview": "name: Warn on opening a PR to `release`\non:\n  pull_request_target:\n    types: [opened]\n    branches: [release]\npermissio"
  },
  {
    "path": ".github/workflows/publish-prerelease.yml",
    "chars": 850,
    "preview": "name: Publish prerelease and update PR\n\non:\n  workflow_call:\n  workflow_dispatch:\n\njobs:\n  update-pr:\n    concurrency: p"
  },
  {
    "path": ".github/workflows/push-release.yml",
    "chars": 819,
    "preview": "name: Create release when pushing to `release`\n\non:\n  push:\n    branches: [release]\n  workflow_dispatch:\n\nconcurrency: m"
  },
  {
    "path": ".github/workflows/rollback.yml",
    "chars": 542,
    "preview": "name: rollback\n\non:\n  workflow_dispatch:\n\nconcurrency: prr:deploy\n\njobs:\n  pr:\n    runs-on: ubuntu-latest\n\n    steps:\n  "
  },
  {
    "path": ".github/workflows/test.yml",
    "chars": 1167,
    "preview": "name: Test and maybe release\n\non:\n  pull_request_target:\n    branches: [ main ]\n  push:\n    branches: [ main ]\n  workflo"
  },
  {
    "path": ".gitignore",
    "chars": 88,
    "preview": "/coverage\n/node_modules\n/jsconfig.json\n/npm-debug.log\n/.vscode\n/.DS_Store\n/.eslintcache\n"
  },
  {
    "path": ".npmignore",
    "chars": 611,
    "preview": "# Development-specific files\n/.deploy.env\n/.editorconfig\n/.eslintrc.js\n/.eslintcache\n/.eslintignore\n/.gitattributes\n/.gi"
  },
  {
    "path": "LICENSE",
    "chars": 1076,
    "preview": "The MIT License (MIT)\n\nCopyright (c) 2017 Leo Horie\n\nPermission is hereby granted, free of charge, to any person obtaini"
  },
  {
    "path": "README.md",
    "chars": 2957,
    "preview": "# Mithril.js\n\n[![npm Version](https://img.shields.io/npm/v/mithril.svg)](https://www.npmjs.com/package/mithril) &nbsp;\n["
  },
  {
    "path": "api/mount-redraw.js",
    "chars": 1053,
    "preview": "\"use strict\"\n\nvar Vnode = require(\"../render/vnode\")\n\nmodule.exports = function(render, schedule, console) {\n\tvar subscr"
  },
  {
    "path": "api/router.js",
    "chars": 8100,
    "preview": "\"use strict\"\n\nvar Vnode = require(\"../render/vnode\")\nvar hyperscript = require(\"../render/hyperscript\")\n\nvar decodeURICo"
  },
  {
    "path": "api/tests/test-mountRedraw.js",
    "chars": 13589,
    "preview": "\"use strict\"\n\n// Low-priority TODO: remove the dependency on the renderer here.\nvar o = require(\"ospec\")\nvar components "
  },
  {
    "path": "api/tests/test-router.js",
    "chars": 55772,
    "preview": "\"use strict\"\n\n// Low-priority TODO: remove the dependency on the renderer here.\nvar o = require(\"ospec\")\nvar browserMock"
  },
  {
    "path": "api/tests/test-routerGetSet.js",
    "chars": 7930,
    "preview": "\"use strict\"\n\n// Low-priority TODO: remove the dependency on the renderer here.\nvar o = require(\"ospec\")\nvar browserMock"
  },
  {
    "path": "browser.js",
    "chars": 116,
    "preview": "\"use strict\"\n\nvar m = require(\"./index\")\nif (typeof module !== \"undefined\") module[\"exports\"] = m\nelse window.m = m\n"
  },
  {
    "path": "docs/code-of-conduct.md",
    "chars": 3556,
    "preview": "<!--meta-description\nCode of Conduct Covenant for contributors to the Mithril.js project\n-->\n\n# Contributor Covenant Cod"
  },
  {
    "path": "docs/contributing.md",
    "chars": 7546,
    "preview": "<!--meta-description\nContribution guide for Mithril.js\n-->\n# Contributing FAQs\n\n- [How do I go about contributing ideas "
  },
  {
    "path": "docs/credits.md",
    "chars": 2257,
    "preview": "<!--meta-description\nList of especially notable contributors to Mithril.js\n-->\n\n# Credits\n\nMithril.js was originally wri"
  },
  {
    "path": "docs/recent-changes.md",
    "chars": 50843,
    "preview": "\n# Release v2.3.8\n\n### Patch Changes\n\n#### [refactor execSelector (@kfule)](https://github.com/MithrilJS/mithril.js/pull"
  },
  {
    "path": "docs/releasing.md",
    "chars": 1753,
    "preview": "<!--meta-description\nDescribes how we do releases of Mithril.js\n-->\n\n# Mithril.js Release Processes\n\nMithril.js' release"
  },
  {
    "path": "hyperscript.js",
    "chars": 193,
    "preview": "\"use strict\"\n\nvar hyperscript = require(\"./render/hyperscript\")\n\nhyperscript.trust = require(\"./render/trust\")\nhyperscri"
  },
  {
    "path": "index.js",
    "chars": 793,
    "preview": "\"use strict\"\n\nvar hyperscript = require(\"./hyperscript\")\nvar mountRedraw = require(\"./mount-redraw\")\nvar request = requi"
  },
  {
    "path": "mithril.js",
    "chars": 67767,
    "preview": ";(function() {\n\"use strict\"\nfunction Vnode(tag, key, attrs0, children, text, dom) {\n\treturn {tag: tag, key: key, attrs: "
  },
  {
    "path": "mount-redraw.js",
    "chars": 229,
    "preview": "\"use strict\"\n\nvar render = require(\"./render\")\n\nmodule.exports = require(\"./api/mount-redraw\")(render, typeof requestAni"
  },
  {
    "path": "mount.js",
    "chars": 63,
    "preview": "\"use strict\"\n\nmodule.exports = require(\"./mount-redraw\").mount\n"
  },
  {
    "path": "package.json",
    "chars": 1141,
    "preview": "{\n  \"name\": \"mithril\",\n  \"version\": \"2.3.8\",\n  \"description\": \"A framework for building brilliant applications\",\n  \"auth"
  },
  {
    "path": "pathname/build.js",
    "chars": 1772,
    "preview": "\"use strict\"\n\nvar buildQueryString = require(\"../querystring/build\")\n\n// Returns `path` from `template` + `params`\nmodul"
  },
  {
    "path": "pathname/compileTemplate.js",
    "chars": 1842,
    "preview": "\"use strict\"\n\nvar parsePathname = require(\"./parse\")\nvar decodeURIComponentSafe = require(\"../util/decodeURIComponentSaf"
  },
  {
    "path": "pathname/parse.js",
    "chars": 576,
    "preview": "\"use strict\"\n\nvar parseQueryString = require(\"../querystring/parse\")\n\n// Returns `{path, params}` from `url`\nmodule.expo"
  },
  {
    "path": "pathname/tests/test-buildPathname.js",
    "chars": 4148,
    "preview": "\"use strict\"\n\nvar o = require(\"ospec\")\nvar buildPathname = require(\"../../pathname/build\")\n\no.spec(\"buildPathname\", func"
  },
  {
    "path": "pathname/tests/test-compileTemplate.js",
    "chars": 9413,
    "preview": "\"use strict\"\n\nvar o = require(\"ospec\")\nvar parsePathname = require(\"../../pathname/parse\")\nvar compileTemplate = require"
  },
  {
    "path": "pathname/tests/test-parsePathname.js",
    "chars": 2938,
    "preview": "\"use strict\"\n\nvar o = require(\"ospec\")\nvar parsePathname = require(\"../../pathname/parse\")\n\no.spec(\"parsePathname\", func"
  },
  {
    "path": "performance/index.html",
    "chars": 293,
    "preview": "<!DOCTYPE html>\n<html>\n\t<head>\n\t\t<meta charset=\"utf-8\">\n\t\t<script src=\"../node_modules/lodash/lodash.js\"></script>\n\t\t<sc"
  },
  {
    "path": "performance/test-perf.js",
    "chars": 12357,
    "preview": "\"use strict\"\n\n/* Based off of preact's perf tests, so including their MIT license */\n/*\nThe MIT License (MIT)\n\nCopyright"
  },
  {
    "path": "querystring/build.js",
    "chars": 672,
    "preview": "\"use strict\"\n\nmodule.exports = function(object) {\n\tif (Object.prototype.toString.call(object) !== \"[object Object]\") ret"
  },
  {
    "path": "querystring/parse.js",
    "chars": 1474,
    "preview": "\"use strict\"\n\nvar decodeURIComponentSafe = require(\"../util/decodeURIComponentSafe\")\n\nmodule.exports = function(string) "
  },
  {
    "path": "querystring/tests/test-buildQueryString.js",
    "chars": 2481,
    "preview": "\"use strict\"\n\nvar o = require(\"ospec\")\nvar buildQueryString = require(\"../../querystring/build\")\n\no.spec(\"buildQueryStri"
  },
  {
    "path": "querystring/tests/test-parseQueryString.js",
    "chars": 4190,
    "preview": "\"use strict\"\n\nvar o = require(\"ospec\")\nvar parseQueryString = require(\"../../querystring/parse\")\n\no.spec(\"parseQueryStri"
  },
  {
    "path": "redraw.js",
    "chars": 64,
    "preview": "\"use strict\"\n\nmodule.exports = require(\"./mount-redraw\").redraw\n"
  },
  {
    "path": "render/cachedAttrsIsStaticMap.js",
    "chars": 547,
    "preview": "\"use strict\"\n\nvar emptyAttrs = require(\"./emptyAttrs\")\n\n// This Map manages the following:\n// - Whether an attrs is cach"
  },
  {
    "path": "render/delayedRemoval.js",
    "chars": 43,
    "preview": "\"use strict\"\n\nmodule.exports = new WeakMap\n"
  },
  {
    "path": "render/domFor.js",
    "chars": 492,
    "preview": "\"use strict\"\n\nvar delayedRemoval = require(\"./delayedRemoval\")\n\nfunction *domFor(vnode) {\n\t// To avoid unintended mangli"
  },
  {
    "path": "render/emptyAttrs.js",
    "chars": 118,
    "preview": "\"use strict\"\n\n// This is an attrs object that is used by default when attrs is undefined or null.\nmodule.exports = {}\n"
  },
  {
    "path": "render/fragment.js",
    "chars": 337,
    "preview": "\"use strict\"\n\nvar Vnode = require(\"../render/vnode\")\nvar hyperscriptVnode = require(\"./hyperscriptVnode\")\n\nmodule.export"
  },
  {
    "path": "render/hyperscript.js",
    "chars": 3219,
    "preview": "\"use strict\"\n\nvar Vnode = require(\"../render/vnode\")\nvar hyperscriptVnode = require(\"./hyperscriptVnode\")\nvar hasOwn = r"
  },
  {
    "path": "render/hyperscriptVnode.js",
    "chars": 926,
    "preview": "\"use strict\"\n\nvar Vnode = require(\"../render/vnode\")\n\n// Note: the processing of variadic parameters is perf-sensitive.\n"
  },
  {
    "path": "render/render.js",
    "chars": 35662,
    "preview": "\"use strict\"\n\nvar Vnode = require(\"./vnode\")\nvar delayedRemoval = require(\"./delayedRemoval\")\nvar domFor = require(\"./do"
  },
  {
    "path": "render/tests/.eslintrc.js",
    "chars": 227,
    "preview": "\"use strict\"\n\nmodule.exports = {\n\t\"extends\": \"../../.eslintrc.js\",\n\t\"env\": {\n\t\t\"browser\": null,\n\t\t\"node\": true,\n\t\t\"es202"
  },
  {
    "path": "render/tests/manual/case-handling.html",
    "chars": 1910,
    "preview": "<!DOCTYPE html>\n<html>\n    <head>\n        <meta charset=\"utf-8\">\n    </head>\n    <body>\n        <p>This is a test for sp"
  },
  {
    "path": "render/tests/manual/iframe.html",
    "chars": 589,
    "preview": "<!DOCTYPE html>\n<html>\n    <head>\n        <meta charset=\"utf-8\">\n    </head>\n    <body>\n        <div id=\"root\"></div>\n  "
  },
  {
    "path": "render/tests/manual/index.html",
    "chars": 262,
    "preview": "<html>\n    <body>\n        Various parent website content.\n        There should be a clickable button below, which is ins"
  },
  {
    "path": "render/tests/manual/minlength-input.html",
    "chars": 1564,
    "preview": "<!DOCTYPE html>\n<html>\n    <head>\n        <meta charset=\"utf-8\">\n    </head>\n    <body>\n        This is minlength validi"
  },
  {
    "path": "render/tests/manual/minlength-textarea.html",
    "chars": 1559,
    "preview": "<!DOCTYPE html>\n<html>\n    <head>\n        <meta charset=\"utf-8\">\n    </head>\n    <body>\n        This is minlength validi"
  },
  {
    "path": "render/tests/test-attributes.js",
    "chars": 19848,
    "preview": "\"use strict\"\n\nvar o = require(\"ospec\")\nvar domMock = require(\"../../test-utils/domMock\")\nvar vdom = require(\"../../rende"
  },
  {
    "path": "render/tests/test-component.js",
    "chars": 31597,
    "preview": "\"use strict\"\n\nvar o = require(\"ospec\")\nvar components = require(\"../../test-utils/components\")\nvar domMock = require(\".."
  },
  {
    "path": "render/tests/test-createElement.js",
    "chars": 3438,
    "preview": "\"use strict\"\n\nvar o = require(\"ospec\")\nvar domMock = require(\"../../test-utils/domMock\")\nvar vdom = require(\"../../rende"
  },
  {
    "path": "render/tests/test-createFragment.js",
    "chars": 1293,
    "preview": "\"use strict\"\n\nvar o = require(\"ospec\")\nvar domMock = require(\"../../test-utils/domMock\")\nvar vdom = require(\"../../rende"
  },
  {
    "path": "render/tests/test-createHTML.js",
    "chars": 3695,
    "preview": "\"use strict\"\n\nvar o = require(\"ospec\")\nvar domMock = require(\"../../test-utils/domMock\")\nvar vdom = require(\"../../rende"
  },
  {
    "path": "render/tests/test-createNodes.js",
    "chars": 1548,
    "preview": "\"use strict\"\n\nvar o = require(\"ospec\")\nvar domMock = require(\"../../test-utils/domMock\")\nvar vdom = require(\"../../rende"
  },
  {
    "path": "render/tests/test-createText.js",
    "chars": 1614,
    "preview": "\"use strict\"\n\nvar o = require(\"ospec\")\nvar domMock = require(\"../../test-utils/domMock\")\nvar vdom = require(\"../../rende"
  },
  {
    "path": "render/tests/test-domFor.js",
    "chars": 32182,
    "preview": "\"use strict\"\n\nconst o = require(\"ospec\")\nconst callAsync = require(\"../../test-utils/callAsync\")\nconst components = requ"
  },
  {
    "path": "render/tests/test-event.js",
    "chars": 25849,
    "preview": "\"use strict\"\n\nvar o = require(\"ospec\")\nvar callAsync = require(\"../../test-utils/callAsync\")\nvar domMock = require(\"../."
  },
  {
    "path": "render/tests/test-fragment.js",
    "chars": 6385,
    "preview": "\"use strict\"\n\nvar o = require(\"ospec\")\nvar fragment = require(\"../../render/fragment\")\nvar m = require(\"../../render/hyp"
  },
  {
    "path": "render/tests/test-hyperscript.js",
    "chars": 19823,
    "preview": "\"use strict\"\n\nvar o = require(\"ospec\")\nvar m = require(\"../../render/hyperscript\")\n\no.spec(\"hyperscript\", function() {\n\t"
  },
  {
    "path": "render/tests/test-input.js",
    "chars": 7527,
    "preview": "\"use strict\"\n\nvar o = require(\"ospec\")\nvar domMock = require(\"../../test-utils/domMock\")\nvar vdom = require(\"../../rende"
  },
  {
    "path": "render/tests/test-normalize.js",
    "chars": 1333,
    "preview": "\"use strict\"\n\nvar o = require(\"ospec\")\nvar Vnode = require(\"../../render/vnode\")\n\no.spec(\"normalize\", function() {\n\to(\"n"
  },
  {
    "path": "render/tests/test-normalizeChildren.js",
    "chars": 2609,
    "preview": "\"use strict\"\n\nvar o = require(\"ospec\")\nvar Vnode = require(\"../../render/vnode\")\n\no.spec(\"normalizeChildren\", function()"
  },
  {
    "path": "render/tests/test-normalizeComponentChildren.js",
    "chars": 824,
    "preview": "\"use strict\"\n\nvar o = require(\"ospec\")\nvar m = require(\"../../render/hyperscript\")\nvar domMock = require(\"../../test-uti"
  },
  {
    "path": "render/tests/test-onbeforeremove.js",
    "chars": 4980,
    "preview": "\"use strict\"\n\nvar o = require(\"ospec\")\nvar callAsync = require(\"../../test-utils/callAsync\")\nvar components = require(\"."
  },
  {
    "path": "render/tests/test-onbeforeupdate.js",
    "chars": 9994,
    "preview": "\"use strict\"\n\nvar o = require(\"ospec\")\nvar components = require(\"../../test-utils/components\")\nvar domMock = require(\".."
  },
  {
    "path": "render/tests/test-oncreate.js",
    "chars": 5843,
    "preview": "\"use strict\"\n\nvar o = require(\"ospec\")\nvar domMock = require(\"../../test-utils/domMock\")\nvar vdom = require(\"../../rende"
  },
  {
    "path": "render/tests/test-oninit.js",
    "chars": 5510,
    "preview": "\"use strict\"\n\nvar o = require(\"ospec\")\nvar domMock = require(\"../../test-utils/domMock\")\nvar vdom = require(\"../../rende"
  },
  {
    "path": "render/tests/test-onremove.js",
    "chars": 10763,
    "preview": "\"use strict\"\n\nvar o = require(\"ospec\")\nvar components = require(\"../../test-utils/components\")\nvar domMock = require(\".."
  },
  {
    "path": "render/tests/test-onupdate.js",
    "chars": 4265,
    "preview": "\"use strict\"\n\nvar o = require(\"ospec\")\nvar domMock = require(\"../../test-utils/domMock\")\nvar vdom = require(\"../../rende"
  },
  {
    "path": "render/tests/test-render-hyperscript-integration.js",
    "chars": 19901,
    "preview": "\"use strict\"\n\nvar o = require(\"ospec\")\nvar m = require(\"../../render/hyperscript\")\nvar domMock = require(\"../../test-uti"
  },
  {
    "path": "render/tests/test-render.js",
    "chars": 10337,
    "preview": "\"use strict\"\n\nvar o = require(\"ospec\")\nvar domMock = require(\"../../test-utils/domMock\")\nvar vdom = require(\"../../rende"
  },
  {
    "path": "render/tests/test-textContent.js",
    "chars": 5061,
    "preview": "\"use strict\"\n\nvar o = require(\"ospec\")\nvar domMock = require(\"../../test-utils/domMock\")\nvar vdom = require(\"../../rende"
  },
  {
    "path": "render/tests/test-trust.js",
    "chars": 658,
    "preview": "\"use strict\"\n\nvar o = require(\"ospec\")\nvar trust = require(\"../../render/trust\")\n\no.spec(\"trust\", function() {\n\to(\"works"
  },
  {
    "path": "render/tests/test-updateElement.js",
    "chars": 16692,
    "preview": "\"use strict\"\n\nvar o = require(\"ospec\")\nvar domMock = require(\"../../test-utils/domMock\")\nvar vdom = require(\"../../rende"
  },
  {
    "path": "render/tests/test-updateFragment.js",
    "chars": 1803,
    "preview": "\"use strict\"\n\nvar o = require(\"ospec\")\nvar domMock = require(\"../../test-utils/domMock\")\nvar vdom = require(\"../../rende"
  },
  {
    "path": "render/tests/test-updateHTML.js",
    "chars": 3406,
    "preview": "\"use strict\"\n\nvar o = require(\"ospec\")\nvar domMock = require(\"../../test-utils/domMock\")\nvar vdom = require(\"../../rende"
  },
  {
    "path": "render/tests/test-updateNodes.js",
    "chars": 41149,
    "preview": "\"use strict\"\n\nvar o = require(\"ospec\")\nvar components = require(\"../../test-utils/components\")\nvar domMock = require(\".."
  },
  {
    "path": "render/tests/test-updateNodesFuzzer.js",
    "chars": 5624,
    "preview": "\"use strict\"\n\nvar o = require(\"ospec\")\nvar domMock = require(\"../../test-utils/domMock\")\nvar vdom = require(\"../../rende"
  },
  {
    "path": "render/tests/test-updateText.js",
    "chars": 1883,
    "preview": "\"use strict\"\n\nvar o = require(\"ospec\")\nvar domMock = require(\"../../test-utils/domMock\")\nvar vdom = require(\"../../rende"
  },
  {
    "path": "render/trust.js",
    "chars": 188,
    "preview": "\"use strict\"\n\nvar Vnode = require(\"../render/vnode\")\n\nmodule.exports = function(html) {\n\tif (html == null) html = \"\"\n\tre"
  },
  {
    "path": "render/vnode.js",
    "chars": 1623,
    "preview": "\"use strict\"\n\nfunction Vnode(tag, key, attrs, children, text, dom) {\n\treturn {tag: tag, key: key, attrs: attrs, children"
  },
  {
    "path": "render.js",
    "chars": 60,
    "preview": "\"use strict\"\n\nmodule.exports = require(\"./render/render\")()\n"
  },
  {
    "path": "request/request.js",
    "chars": 6803,
    "preview": "\"use strict\"\n\nvar buildPathname = require(\"../pathname/build\")\nvar hasOwn = require(\"../util/hasOwn\")\n\nmodule.exports = "
  },
  {
    "path": "request/tests/test-request.js",
    "chars": 28194,
    "preview": "\"use strict\"\n\nvar o = require(\"ospec\")\nvar callAsync = require(\"../../test-utils/callAsync\")\nvar xhrMock = require(\"../."
  },
  {
    "path": "request.js",
    "chars": 172,
    "preview": "\"use strict\"\n\nvar mountRedraw = require(\"./mount-redraw\")\n\nmodule.exports = require(\"./request/request\")(typeof window !"
  },
  {
    "path": "route.js",
    "chars": 160,
    "preview": "\"use strict\"\n\nvar mountRedraw = require(\"./mount-redraw\")\n\nmodule.exports = require(\"./api/router\")(typeof window !== \"u"
  },
  {
    "path": "scripts/.eslintrc.js",
    "chars": 224,
    "preview": "\"use strict\"\n\nmodule.exports = {\n\t\"extends\": \"../.eslintrc.js\",\n\t\"env\": {\n\t\t\"browser\": null,\n\t\t\"node\": true,\n\t\t\"es2022\":"
  },
  {
    "path": "scripts/_bundler-impl.js",
    "chars": 6846,
    "preview": "\"use strict\"\n\nconst fs = require(\"fs\")\nconst path = require(\"path\")\nconst execFileSync = require(\"child_process\").execFi"
  },
  {
    "path": "scripts/bundler-readme.md",
    "chars": 2258,
    "preview": "# bundler.js\n\nSimplistic CommonJS module bundler\n\nVersion: 0.1\nLicense: MIT\n\n## About\n\nThis bundler attempts to aggressi"
  },
  {
    "path": "scripts/bundler.js",
    "chars": 2300,
    "preview": "\"use strict\"\n\nconst fs = require(\"fs\")\nconst zlib = require(\"zlib\")\nconst chokidar = require(\"chokidar\")\nconst Terser = "
  },
  {
    "path": "scripts/minify-stream.js",
    "chars": 1538,
    "preview": "#!/usr/bin/env node\n/* eslint-disable no-process-exit */\n\"use strict\"\n\nprocess.on(\"unhandledRejection\", (e) => {\n\tproces"
  },
  {
    "path": "scripts/set-versioned-branch.sh",
    "chars": 425,
    "preview": "#!/usr/bin/env bash\nset -euo pipefail\n\nbase=\"$1\"\n\nif [[ -z \"$base\" ]]; then\n    echo '::error::Base branch is missing. I"
  },
  {
    "path": "scripts/tests/test-bundler.js",
    "chars": 17410,
    "preview": "\"use strict\"\n\nconst fs = require(\"fs\")\nconst util = require(\"util\")\nconst path = require(\"path\")\nconst access = util.pro"
  },
  {
    "path": "stream/stream.js",
    "chars": 4406,
    "preview": "/* eslint-disable */\n;(function() {\n\"use strict\"\n/* eslint-enable */\nStream.SKIP = {}\nStream.lift = lift\nStream.scan = s"
  },
  {
    "path": "stream/tests/test-scan.js",
    "chars": 1357,
    "preview": "\"use strict\"\n\nvar o = require(\"ospec\")\nvar stream = require(\"../stream\")\n\no.spec(\"scan\", function() {\n\to(\"defaults to se"
  },
  {
    "path": "stream/tests/test-scanMerge.js",
    "chars": 731,
    "preview": "\"use strict\"\n\nvar o = require(\"ospec\")\nvar stream = require(\"../stream\")\n\no.spec(\"scanMerge\", function() {\n\to(\"defaults "
  },
  {
    "path": "stream/tests/test-stream.js",
    "chars": 17763,
    "preview": "\"use strict\"\n\nvar o = require(\"ospec\")\nvar Stream = require(\"../stream\")\n\no.spec(\"stream\", function() {\n\to.spec(\"stream\""
  },
  {
    "path": "stream.js",
    "chars": 58,
    "preview": "\"use strict\"\n\nmodule.exports = require(\"./stream/stream\")\n"
  },
  {
    "path": "test-utils/browserMock.js",
    "chars": 424,
    "preview": "\"use strict\"\n\nvar pushStateMock = require(\"./pushStateMock\")\nvar domMock = require(\"./domMock\")\nvar xhrMock = require(\"."
  },
  {
    "path": "test-utils/callAsync.js",
    "chars": 42,
    "preview": "\"use strict\"\n\nmodule.exports = setTimeout\n"
  },
  {
    "path": "test-utils/components.js",
    "chars": 729,
    "preview": "\"use strict\"\n\nvar m = require(\"../render/hyperscript\")\n\nmodule.exports = [\n\t{\n\t\tkind: \"POJO\",\n\t\tcreate: function(methods"
  },
  {
    "path": "test-utils/domMock.js",
    "chars": 25891,
    "preview": "\"use strict\"\n\n/*\nKnown limitations:\n- the innerHTML setter and the DOMParser only support a small subset of the true HTM"
  },
  {
    "path": "test-utils/parseURL.js",
    "chars": 2033,
    "preview": "\"use strict\"\n\nmodule.exports = function parseURL(url, root) {\n\tvar data = {}\n\tvar protocolIndex = url.indexOf(\"://\")\n\tva"
  },
  {
    "path": "test-utils/pushStateMock.js",
    "chars": 5068,
    "preview": "\"use strict\"\n\nvar parseURL = require(\"../test-utils/parseURL\")\nvar callAsync = require(\"../test-utils/callAsync\")\n\nfunct"
  },
  {
    "path": "test-utils/tests/test-browserMock.js",
    "chars": 1123,
    "preview": "\"use strict\"\n\nvar o = require(\"ospec\")\nvar browserMock = require(\"../../test-utils/browserMock\")\nvar callAsync = require"
  },
  {
    "path": "test-utils/tests/test-callAsync.js",
    "chars": 483,
    "preview": "\"use strict\"\n\nvar o = require(\"ospec\")\nvar callAsync = require(\"../../test-utils/callAsync\")\n\no.spec(\"callAsync\", functi"
  },
  {
    "path": "test-utils/tests/test-components.js",
    "chars": 1446,
    "preview": "\"use strict\"\n\nvar o = require(\"ospec\")\nvar components = require(\"../../test-utils/components\")\nvar m = require(\"../../re"
  },
  {
    "path": "test-utils/tests/test-domMock.js",
    "chars": 60628,
    "preview": "\"use strict\"\n\nvar o = require(\"ospec\")\nvar domMock = require(\"../../test-utils/domMock\")\n\no.spec(\"domMock\", function() {"
  },
  {
    "path": "test-utils/tests/test-parseURL.js",
    "chars": 5315,
    "preview": "\"use strict\"\n\nvar o = require(\"ospec\")\nvar parseURL = require(\"../../test-utils/parseURL\")\n\no.spec(\"parseURL\", function("
  },
  {
    "path": "test-utils/tests/test-pushStateMock.js",
    "chars": 22106,
    "preview": "\"use strict\"\n\nvar o = require(\"ospec\")\nvar pushStateMock = require(\"../../test-utils/pushStateMock\")\nvar callAsync = req"
  },
  {
    "path": "test-utils/tests/test-throttleMock.js",
    "chars": 1039,
    "preview": "\"use strict\"\n\nvar o = require(\"ospec\")\nvar throttleMocker = require(\"../../test-utils/throttleMock\")\n\no.spec(\"throttleMo"
  },
  {
    "path": "test-utils/tests/test-xhrMock.js",
    "chars": 2545,
    "preview": "\"use strict\"\n\nvar o = require(\"ospec\")\nvar xhrMock = require(\"../../test-utils/xhrMock\")\n\no.spec(\"xhrMock\", function() {"
  },
  {
    "path": "test-utils/throttleMock.js",
    "chars": 278,
    "preview": "\"use strict\"\n\nmodule.exports = function() {\n\tvar queue = []\n\treturn {\n\t\tschedule: function(fn) {\n\t\t\tqueue.push(fn)\n\t\t},\n"
  },
  {
    "path": "test-utils/xhrMock.js",
    "chars": 4395,
    "preview": "\"use strict\"\n\nvar callAsync = require(\"../test-utils/callAsync\")\nvar parseURL = require(\"../test-utils/parseURL\")\nvar pa"
  },
  {
    "path": "tests/test-api.js",
    "chars": 4217,
    "preview": "\"use strict\"\n\nvar o = require(\"ospec\")\nvar browserMock = require(\"../test-utils/browserMock\")\nvar components = require(\""
  },
  {
    "path": "util/censor.js",
    "chars": 1275,
    "preview": "\"use strict\"\n\n// Note: this is mildly perf-sensitive.\n//\n// It does *not* use `delete` - dynamic `delete`s usually cause"
  },
  {
    "path": "util/decodeURIComponentSafe.js",
    "chars": 1381,
    "preview": "\"use strict\"\n\n/*\nPercent encodings encode UTF-8 bytes, so this regexp needs to match that.\nHere's how UTF-8 encodes stuf"
  },
  {
    "path": "util/hasOwn.js",
    "chars": 92,
    "preview": "// This exists so I'm only saving it once.\n\"use strict\"\n\nmodule.exports = {}.hasOwnProperty\n"
  },
  {
    "path": "util/tests/test-censor.js",
    "chars": 5518,
    "preview": "\"use strict\"\n\nvar o = require(\"ospec\")\nvar censor = require(\"../../util/censor\")\n\no.spec(\"censor\", function() {\n\to.spec("
  },
  {
    "path": "util/tests/test-decodeURIComponentSafe.js",
    "chars": 5192,
    "preview": "\"use strict\"\n\nvar o = require(\"ospec\")\nvar decodeURIComponentSafe = require(\"../../util/decodeURIComponentSafe\")\n\no.spec"
  }
]

// ... and 1 more files (download for full content)

About this extraction

This page contains the full source code of the MithrilJS/mithril.js GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 138 files (869.2 KB), approximately 260.0k tokens, and a symbol index with 307 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!