├── .remarkignore ├── .prettierignore ├── test ├── fixtures │ ├── toml-deep │ │ ├── config.json │ │ ├── input.md │ │ └── tree.json │ ├── toml-advanced │ │ ├── config.json │ │ ├── input.md │ │ └── tree.json │ ├── toml-empty │ │ ├── config.json │ │ ├── input.md │ │ └── tree.json │ ├── yaml-unconfigured │ │ ├── config.json │ │ ├── input.md │ │ ├── output.md │ │ └── tree.json │ ├── config-options-as-string │ │ ├── config.json │ │ ├── input.md │ │ └── tree.json │ ├── custom-empty │ │ ├── input.md │ │ ├── config.json │ │ └── tree.json │ ├── yaml-empty │ │ ├── input.md │ │ └── tree.json │ ├── yaml-unclosed │ │ ├── input.md │ │ ├── output.md │ │ └── tree.json │ ├── toml-unconfigured │ │ ├── input.md │ │ ├── output.md │ │ └── tree.json │ ├── custom-deep │ │ ├── config.json │ │ ├── input.md │ │ └── tree.json │ ├── config-options-as-matter │ │ ├── input.md │ │ ├── config.json │ │ └── tree.json │ ├── custom-fence │ │ ├── config.json │ │ ├── input.md │ │ └── tree.json │ ├── yaml-closed-incorrectly │ │ ├── input.md │ │ ├── output.md │ │ └── tree.json │ ├── custom-fence-short │ │ ├── config.json │ │ ├── input.md │ │ └── tree.json │ ├── custom-fence-long │ │ ├── config.json │ │ ├── input.md │ │ └── tree.json │ ├── toml-default │ │ ├── input.md │ │ ├── output.md │ │ └── tree.json │ ├── yaml-default │ │ ├── input.md │ │ └── tree.json │ ├── custom-yaml-anywhere │ │ ├── config.json │ │ ├── input.md │ │ ├── output.md │ │ └── tree.json │ ├── custom-default │ │ ├── input.md │ │ ├── output.md │ │ └── tree.json │ ├── custom-marker-openclose │ │ ├── config.json │ │ ├── input.md │ │ └── tree.json │ ├── yaml-deep │ │ ├── input.md │ │ └── tree.json │ ├── core-yaml-delayed │ │ ├── output.md │ │ ├── input.md │ │ └── tree.json │ ├── custom-fence-openclose │ │ ├── config.json │ │ ├── input.md │ │ └── tree.json │ ├── core-yaml-not-at-top │ │ ├── input.md │ │ ├── output.md │ │ └── tree.json │ └── yaml-advanced │ │ ├── input.md │ │ └── tree.json └── index.js ├── .npmrc ├── .gitignore ├── index.js ├── .editorconfig ├── .github └── workflows │ ├── bb.yml │ └── main.yml ├── tsconfig.json ├── license ├── lib └── index.js ├── package.json └── readme.md /.remarkignore: -------------------------------------------------------------------------------- 1 | test/ 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | coverage/ 2 | *.md 3 | -------------------------------------------------------------------------------- /test/fixtures/toml-deep/config.json: -------------------------------------------------------------------------------- 1 | ["toml"] 2 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | ignore-scripts=true 2 | package-lock=false 3 | -------------------------------------------------------------------------------- /test/fixtures/toml-advanced/config.json: -------------------------------------------------------------------------------- 1 | ["toml"] 2 | -------------------------------------------------------------------------------- /test/fixtures/toml-empty/config.json: -------------------------------------------------------------------------------- 1 | ["toml"] 2 | -------------------------------------------------------------------------------- /test/fixtures/yaml-unconfigured/config.json: -------------------------------------------------------------------------------- 1 | ["toml"] 2 | -------------------------------------------------------------------------------- /test/fixtures/config-options-as-string/config.json: -------------------------------------------------------------------------------- 1 | "yaml" 2 | -------------------------------------------------------------------------------- /test/fixtures/custom-empty/input.md: -------------------------------------------------------------------------------- 1 | *** 2 | *** 3 | 4 | # Empty 5 | -------------------------------------------------------------------------------- /test/fixtures/toml-empty/input.md: -------------------------------------------------------------------------------- 1 | +++ 2 | +++ 3 | 4 | # Empty 5 | -------------------------------------------------------------------------------- /test/fixtures/yaml-empty/input.md: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | # Empty 5 | -------------------------------------------------------------------------------- /test/fixtures/yaml-unclosed/input.md: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | # Unclosed 4 | -------------------------------------------------------------------------------- /test/fixtures/yaml-unclosed/output.md: -------------------------------------------------------------------------------- 1 | *** 2 | 3 | # Unclosed 4 | -------------------------------------------------------------------------------- /test/fixtures/toml-unconfigured/input.md: -------------------------------------------------------------------------------- 1 | +++ 2 | +++ 3 | 4 | # Empty 5 | -------------------------------------------------------------------------------- /test/fixtures/yaml-unconfigured/input.md: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | # Empty 5 | -------------------------------------------------------------------------------- /test/fixtures/custom-deep/config.json: -------------------------------------------------------------------------------- 1 | [{"type": "jsonml", "marker": "*"}] 2 | -------------------------------------------------------------------------------- /test/fixtures/custom-empty/config.json: -------------------------------------------------------------------------------- 1 | [{"type": "jsonml", "marker": "*"}] 2 | -------------------------------------------------------------------------------- /test/fixtures/toml-unconfigured/output.md: -------------------------------------------------------------------------------- 1 | +++ 2 | +++ 3 | 4 | # Empty 5 | -------------------------------------------------------------------------------- /test/fixtures/config-options-as-matter/input.md: -------------------------------------------------------------------------------- 1 | *** 2 | *** 3 | 4 | # Empty 5 | -------------------------------------------------------------------------------- /test/fixtures/config-options-as-string/input.md: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | # Empty 5 | -------------------------------------------------------------------------------- /test/fixtures/custom-fence/config.json: -------------------------------------------------------------------------------- 1 | [{"type": "jsonml", "fence": "+++"}] 2 | -------------------------------------------------------------------------------- /test/fixtures/yaml-closed-incorrectly/input.md: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | # Incorrect --- 4 | -------------------------------------------------------------------------------- /test/fixtures/yaml-closed-incorrectly/output.md: -------------------------------------------------------------------------------- 1 | *** 2 | 3 | # Incorrect --- 4 | -------------------------------------------------------------------------------- /test/fixtures/yaml-unconfigured/output.md: -------------------------------------------------------------------------------- 1 | *** 2 | 3 | *** 4 | 5 | # Empty 6 | -------------------------------------------------------------------------------- /test/fixtures/config-options-as-matter/config.json: -------------------------------------------------------------------------------- 1 | {"type": "jsonml", "marker": "*"} 2 | -------------------------------------------------------------------------------- /test/fixtures/custom-fence-short/config.json: -------------------------------------------------------------------------------- 1 | [{"type": "jsonml", "fence": "=="}] 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | coverage/ 2 | node_modules/ 3 | .DS_Store 4 | *.d.ts 5 | *.log 6 | yarn.lock 7 | -------------------------------------------------------------------------------- /test/fixtures/custom-fence-long/config.json: -------------------------------------------------------------------------------- 1 | [{"type": "jsonml", "fence": "=f-e*n_c$e"}] 2 | -------------------------------------------------------------------------------- /test/fixtures/toml-default/input.md: -------------------------------------------------------------------------------- 1 | +++ 2 | title= "Title" 3 | +++ 4 | 5 | # Default 6 | -------------------------------------------------------------------------------- /test/fixtures/toml-default/output.md: -------------------------------------------------------------------------------- 1 | +++ 2 | title= "Title" 3 | +++ 4 | 5 | # Default 6 | -------------------------------------------------------------------------------- /test/fixtures/yaml-default/input.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: example 3 | --- 4 | 5 | # Default 6 | -------------------------------------------------------------------------------- /test/fixtures/custom-yaml-anywhere/config.json: -------------------------------------------------------------------------------- 1 | {"type": "yaml", "marker": "-", "anywhere": true} 2 | -------------------------------------------------------------------------------- /test/fixtures/custom-default/input.md: -------------------------------------------------------------------------------- 1 | *** 2 | { 3 | "hello": "World!" 4 | } 5 | *** 6 | 7 | # Default 8 | -------------------------------------------------------------------------------- /test/fixtures/toml-deep/input.md: -------------------------------------------------------------------------------- 1 | +++ 2 | title = "Example" 3 | # A comment +++ 4 | +++ 5 | 6 | # Deep 7 | -------------------------------------------------------------------------------- /test/fixtures/custom-marker-openclose/config.json: -------------------------------------------------------------------------------- 1 | [{"type": "jsonml", "marker": {"open": "<", "close": ">"}}] 2 | -------------------------------------------------------------------------------- /test/fixtures/yaml-deep/input.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: post 3 | object: 4 | key: value --- 5 | --- 6 | 7 | # Deep 8 | -------------------------------------------------------------------------------- /test/fixtures/core-yaml-delayed/output.md: -------------------------------------------------------------------------------- 1 | *** 2 | 3 | ## title: post 4 | 5 | # Later (whitespace before yaml) 6 | -------------------------------------------------------------------------------- /test/fixtures/custom-fence-openclose/config.json: -------------------------------------------------------------------------------- 1 | [{"type": "jsonml", "fence": {"open": "$$OPEN", "close": "CLOSE##"}}] 2 | -------------------------------------------------------------------------------- /test/fixtures/core-yaml-delayed/input.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | --- 4 | title: post 5 | --- 6 | 7 | # Later (whitespace before yaml) 8 | -------------------------------------------------------------------------------- /test/fixtures/custom-deep/input.md: -------------------------------------------------------------------------------- 1 | *** 2 | { 3 | title: "Custom", 4 | // A comment *** 5 | } 6 | *** 7 | 8 | # Deep 9 | -------------------------------------------------------------------------------- /test/fixtures/custom-default/output.md: -------------------------------------------------------------------------------- 1 | *** 2 | 3 | { 4 | "hello": "World!" 5 | } 6 | 7 | *** 8 | 9 | # Default 10 | -------------------------------------------------------------------------------- /test/fixtures/custom-fence/input.md: -------------------------------------------------------------------------------- 1 | +++ 2 | { 3 | title: "Custom", 4 | // A comment *** 5 | } 6 | +++ 7 | 8 | # Deep 9 | -------------------------------------------------------------------------------- /test/fixtures/custom-yaml-anywhere/input.md: -------------------------------------------------------------------------------- 1 | # YAML not at the top 2 | 3 | --- 4 | title: post 5 | --- 6 | 7 | some text 8 | -------------------------------------------------------------------------------- /test/fixtures/custom-yaml-anywhere/output.md: -------------------------------------------------------------------------------- 1 | # YAML not at the top 2 | 3 | --- 4 | title: post 5 | --- 6 | 7 | some text 8 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @typedef {import('./lib/index.js').Options} Options 3 | */ 4 | 5 | export {default} from './lib/index.js' 6 | -------------------------------------------------------------------------------- /test/fixtures/custom-fence-short/input.md: -------------------------------------------------------------------------------- 1 | == 2 | { 3 | title: "Custom", 4 | // A comment *** 5 | } 6 | == 7 | 8 | # Deep 9 | -------------------------------------------------------------------------------- /test/fixtures/core-yaml-not-at-top/input.md: -------------------------------------------------------------------------------- 1 | # Two horizontal rules 2 | 3 | --- 4 | A horizontal rule 5 | --- 6 | 7 | and another. 8 | -------------------------------------------------------------------------------- /test/fixtures/core-yaml-not-at-top/output.md: -------------------------------------------------------------------------------- 1 | # Two horizontal rules 2 | 3 | *** 4 | 5 | ## A horizontal rule 6 | 7 | and another. 8 | -------------------------------------------------------------------------------- /test/fixtures/custom-marker-openclose/input.md: -------------------------------------------------------------------------------- 1 | <<< 2 | { 3 | title: "Custom", 4 | // A comment *** 5 | } 6 | >>> 7 | 8 | # Deep 9 | -------------------------------------------------------------------------------- /test/fixtures/custom-fence-openclose/input.md: -------------------------------------------------------------------------------- 1 | $$OPEN 2 | { 3 | title: "Custom", 4 | // A comment *** 5 | } 6 | CLOSE## 7 | 8 | # Deep 9 | -------------------------------------------------------------------------------- /test/fixtures/custom-fence-long/input.md: -------------------------------------------------------------------------------- 1 | =f-e*n_c$e 2 | { 3 | title: "Custom", 4 | // A comment *** 5 | } 6 | =f-e*n_c$e 7 | 8 | # Deep 9 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | -------------------------------------------------------------------------------- /.github/workflows/bb.yml: -------------------------------------------------------------------------------- 1 | name: bb 2 | on: 3 | issues: 4 | types: [opened, reopened, edited, closed, labeled, unlabeled] 5 | pull_request_target: 6 | types: [opened, reopened, edited, closed, labeled, unlabeled] 7 | jobs: 8 | main: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: unifiedjs/beep-boop-beta@main 12 | with: 13 | repo-token: ${{secrets.GITHUB_TOKEN}} 14 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "checkJs": true, 4 | "customConditions": ["development"], 5 | "declaration": true, 6 | "emitDeclarationOnly": true, 7 | "exactOptionalPropertyTypes": true, 8 | "lib": ["es2022"], 9 | "module": "node16", 10 | "strict": true, 11 | "target": "es2022" 12 | }, 13 | "exclude": ["coverage/", "node_modules/"], 14 | "include": ["**/*.js"] 15 | } 16 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: main 2 | on: 3 | - pull_request 4 | - push 5 | jobs: 6 | main: 7 | name: ${{matrix.node}} 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v3 11 | - uses: actions/setup-node@v3 12 | with: 13 | node-version: ${{matrix.node}} 14 | - run: npm install 15 | - run: npm test 16 | - uses: codecov/codecov-action@v3 17 | strategy: 18 | matrix: 19 | node: 20 | - lts/gallium 21 | - node 22 | -------------------------------------------------------------------------------- /test/fixtures/toml-advanced/input.md: -------------------------------------------------------------------------------- 1 | +++ 2 | title = "TOML Example" 3 | 4 | [owner] 5 | name = "Tom Preston-Werner" 6 | dob = 1979-05-27T07:32:00-08:00 # First class dates 7 | 8 | [database] 9 | server = "192.168.1.1" 10 | ports = [ 8001, 8001, 8002 ] 11 | connection_max = 5000 12 | enabled = true 13 | 14 | [servers] 15 | 16 | # Indentation (tabs and/or spaces) is allowed but not required 17 | [servers.alpha] 18 | ip = "10.0.0.1" 19 | dc = "eqdc10" 20 | 21 | [servers.beta] 22 | ip = "10.0.0.2" 23 | dc = "eqdc10" 24 | 25 | [clients] 26 | data = [ ["gamma", "delta"], [1, 2] ] 27 | 28 | # Line breaks are OK when inside arrays 29 | hosts = [ 30 | "alpha", 31 | "omega" 32 | ] 33 | +++ 34 | 35 | # Big document 36 | -------------------------------------------------------------------------------- /test/fixtures/yaml-advanced/input.md: -------------------------------------------------------------------------------- 1 | --- 2 | invoice: 34843 3 | date : 2001-01-23 4 | bill-to: &id001 5 | given : Chris 6 | family : Dumars 7 | address: 8 | lines: | 9 | 458 Walkman Dr. 10 | Suite #292 11 | city : Royal Oak 12 | state : MI 13 | postal : 48046 14 | ship-to: *id001 15 | product: 16 | - sku : BL394D 17 | quantity : 4 18 | description : Basketball 19 | price : 450.00 20 | - sku : BL4438H 21 | quantity : 1 22 | description : Super Hoop 23 | price : 2392.00 24 | tax : 251.42 25 | total: 4443.52 26 | comments: > 27 | Late afternoon is best. 28 | Backup contact is Nancy 29 | Billsmer @ 338-4338. 30 | --- 31 | 32 | # Big document 33 | -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) 2017 Titus Wormer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | 'Software'), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /test/fixtures/yaml-unclosed/tree.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "root", 3 | "children": [ 4 | { 5 | "type": "thematicBreak", 6 | "position": { 7 | "start": { 8 | "line": 1, 9 | "column": 1, 10 | "offset": 0 11 | }, 12 | "end": { 13 | "line": 1, 14 | "column": 4, 15 | "offset": 3 16 | } 17 | } 18 | }, 19 | { 20 | "type": "heading", 21 | "depth": 1, 22 | "children": [ 23 | { 24 | "type": "text", 25 | "value": "Unclosed", 26 | "position": { 27 | "start": { 28 | "line": 3, 29 | "column": 3, 30 | "offset": 7 31 | }, 32 | "end": { 33 | "line": 3, 34 | "column": 11, 35 | "offset": 15 36 | } 37 | } 38 | } 39 | ], 40 | "position": { 41 | "start": { 42 | "line": 3, 43 | "column": 1, 44 | "offset": 5 45 | }, 46 | "end": { 47 | "line": 3, 48 | "column": 11, 49 | "offset": 15 50 | } 51 | } 52 | } 53 | ], 54 | "position": { 55 | "start": { 56 | "line": 1, 57 | "column": 1, 58 | "offset": 0 59 | }, 60 | "end": { 61 | "line": 4, 62 | "column": 1, 63 | "offset": 16 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /test/fixtures/toml-empty/tree.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "root", 3 | "children": [ 4 | { 5 | "type": "toml", 6 | "value": "", 7 | "position": { 8 | "start": { 9 | "line": 1, 10 | "column": 1, 11 | "offset": 0 12 | }, 13 | "end": { 14 | "line": 2, 15 | "column": 4, 16 | "offset": 7 17 | } 18 | } 19 | }, 20 | { 21 | "type": "heading", 22 | "depth": 1, 23 | "children": [ 24 | { 25 | "type": "text", 26 | "value": "Empty", 27 | "position": { 28 | "start": { 29 | "line": 4, 30 | "column": 3, 31 | "offset": 11 32 | }, 33 | "end": { 34 | "line": 4, 35 | "column": 8, 36 | "offset": 16 37 | } 38 | } 39 | } 40 | ], 41 | "position": { 42 | "start": { 43 | "line": 4, 44 | "column": 1, 45 | "offset": 9 46 | }, 47 | "end": { 48 | "line": 4, 49 | "column": 8, 50 | "offset": 16 51 | } 52 | } 53 | } 54 | ], 55 | "position": { 56 | "start": { 57 | "line": 1, 58 | "column": 1, 59 | "offset": 0 60 | }, 61 | "end": { 62 | "line": 5, 63 | "column": 1, 64 | "offset": 17 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /test/fixtures/yaml-empty/tree.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "root", 3 | "children": [ 4 | { 5 | "type": "yaml", 6 | "value": "", 7 | "position": { 8 | "start": { 9 | "line": 1, 10 | "column": 1, 11 | "offset": 0 12 | }, 13 | "end": { 14 | "line": 2, 15 | "column": 4, 16 | "offset": 7 17 | } 18 | } 19 | }, 20 | { 21 | "type": "heading", 22 | "depth": 1, 23 | "children": [ 24 | { 25 | "type": "text", 26 | "value": "Empty", 27 | "position": { 28 | "start": { 29 | "line": 4, 30 | "column": 3, 31 | "offset": 11 32 | }, 33 | "end": { 34 | "line": 4, 35 | "column": 8, 36 | "offset": 16 37 | } 38 | } 39 | } 40 | ], 41 | "position": { 42 | "start": { 43 | "line": 4, 44 | "column": 1, 45 | "offset": 9 46 | }, 47 | "end": { 48 | "line": 4, 49 | "column": 8, 50 | "offset": 16 51 | } 52 | } 53 | } 54 | ], 55 | "position": { 56 | "start": { 57 | "line": 1, 58 | "column": 1, 59 | "offset": 0 60 | }, 61 | "end": { 62 | "line": 5, 63 | "column": 1, 64 | "offset": 17 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /test/fixtures/custom-empty/tree.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "root", 3 | "children": [ 4 | { 5 | "type": "jsonml", 6 | "value": "", 7 | "position": { 8 | "start": { 9 | "line": 1, 10 | "column": 1, 11 | "offset": 0 12 | }, 13 | "end": { 14 | "line": 2, 15 | "column": 4, 16 | "offset": 7 17 | } 18 | } 19 | }, 20 | { 21 | "type": "heading", 22 | "depth": 1, 23 | "children": [ 24 | { 25 | "type": "text", 26 | "value": "Empty", 27 | "position": { 28 | "start": { 29 | "line": 4, 30 | "column": 3, 31 | "offset": 11 32 | }, 33 | "end": { 34 | "line": 4, 35 | "column": 8, 36 | "offset": 16 37 | } 38 | } 39 | } 40 | ], 41 | "position": { 42 | "start": { 43 | "line": 4, 44 | "column": 1, 45 | "offset": 9 46 | }, 47 | "end": { 48 | "line": 4, 49 | "column": 8, 50 | "offset": 16 51 | } 52 | } 53 | } 54 | ], 55 | "position": { 56 | "start": { 57 | "line": 1, 58 | "column": 1, 59 | "offset": 0 60 | }, 61 | "end": { 62 | "line": 5, 63 | "column": 1, 64 | "offset": 17 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /test/fixtures/yaml-closed-incorrectly/tree.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "root", 3 | "children": [ 4 | { 5 | "type": "thematicBreak", 6 | "position": { 7 | "start": { 8 | "line": 1, 9 | "column": 1, 10 | "offset": 0 11 | }, 12 | "end": { 13 | "line": 1, 14 | "column": 4, 15 | "offset": 3 16 | } 17 | } 18 | }, 19 | { 20 | "type": "heading", 21 | "depth": 1, 22 | "children": [ 23 | { 24 | "type": "text", 25 | "value": "Incorrect ---", 26 | "position": { 27 | "start": { 28 | "line": 3, 29 | "column": 3, 30 | "offset": 7 31 | }, 32 | "end": { 33 | "line": 3, 34 | "column": 16, 35 | "offset": 20 36 | } 37 | } 38 | } 39 | ], 40 | "position": { 41 | "start": { 42 | "line": 3, 43 | "column": 1, 44 | "offset": 5 45 | }, 46 | "end": { 47 | "line": 3, 48 | "column": 16, 49 | "offset": 20 50 | } 51 | } 52 | } 53 | ], 54 | "position": { 55 | "start": { 56 | "line": 1, 57 | "column": 1, 58 | "offset": 0 59 | }, 60 | "end": { 61 | "line": 4, 62 | "column": 1, 63 | "offset": 21 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /test/fixtures/config-options-as-matter/tree.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "root", 3 | "children": [ 4 | { 5 | "type": "jsonml", 6 | "value": "", 7 | "position": { 8 | "start": { 9 | "line": 1, 10 | "column": 1, 11 | "offset": 0 12 | }, 13 | "end": { 14 | "line": 2, 15 | "column": 4, 16 | "offset": 7 17 | } 18 | } 19 | }, 20 | { 21 | "type": "heading", 22 | "depth": 1, 23 | "children": [ 24 | { 25 | "type": "text", 26 | "value": "Empty", 27 | "position": { 28 | "start": { 29 | "line": 4, 30 | "column": 3, 31 | "offset": 11 32 | }, 33 | "end": { 34 | "line": 4, 35 | "column": 8, 36 | "offset": 16 37 | } 38 | } 39 | } 40 | ], 41 | "position": { 42 | "start": { 43 | "line": 4, 44 | "column": 1, 45 | "offset": 9 46 | }, 47 | "end": { 48 | "line": 4, 49 | "column": 8, 50 | "offset": 16 51 | } 52 | } 53 | } 54 | ], 55 | "position": { 56 | "start": { 57 | "line": 1, 58 | "column": 1, 59 | "offset": 0 60 | }, 61 | "end": { 62 | "line": 5, 63 | "column": 1, 64 | "offset": 17 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /test/fixtures/config-options-as-string/tree.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "root", 3 | "children": [ 4 | { 5 | "type": "yaml", 6 | "value": "", 7 | "position": { 8 | "start": { 9 | "line": 1, 10 | "column": 1, 11 | "offset": 0 12 | }, 13 | "end": { 14 | "line": 2, 15 | "column": 4, 16 | "offset": 7 17 | } 18 | } 19 | }, 20 | { 21 | "type": "heading", 22 | "depth": 1, 23 | "children": [ 24 | { 25 | "type": "text", 26 | "value": "Empty", 27 | "position": { 28 | "start": { 29 | "line": 4, 30 | "column": 3, 31 | "offset": 11 32 | }, 33 | "end": { 34 | "line": 4, 35 | "column": 8, 36 | "offset": 16 37 | } 38 | } 39 | } 40 | ], 41 | "position": { 42 | "start": { 43 | "line": 4, 44 | "column": 1, 45 | "offset": 9 46 | }, 47 | "end": { 48 | "line": 4, 49 | "column": 8, 50 | "offset": 16 51 | } 52 | } 53 | } 54 | ], 55 | "position": { 56 | "start": { 57 | "line": 1, 58 | "column": 1, 59 | "offset": 0 60 | }, 61 | "end": { 62 | "line": 5, 63 | "column": 1, 64 | "offset": 17 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /test/fixtures/yaml-default/tree.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "root", 3 | "children": [ 4 | { 5 | "type": "yaml", 6 | "value": "title: example", 7 | "position": { 8 | "start": { 9 | "line": 1, 10 | "column": 1, 11 | "offset": 0 12 | }, 13 | "end": { 14 | "line": 3, 15 | "column": 4, 16 | "offset": 22 17 | } 18 | } 19 | }, 20 | { 21 | "type": "heading", 22 | "depth": 1, 23 | "children": [ 24 | { 25 | "type": "text", 26 | "value": "Default", 27 | "position": { 28 | "start": { 29 | "line": 5, 30 | "column": 3, 31 | "offset": 26 32 | }, 33 | "end": { 34 | "line": 5, 35 | "column": 10, 36 | "offset": 33 37 | } 38 | } 39 | } 40 | ], 41 | "position": { 42 | "start": { 43 | "line": 5, 44 | "column": 1, 45 | "offset": 24 46 | }, 47 | "end": { 48 | "line": 5, 49 | "column": 10, 50 | "offset": 33 51 | } 52 | } 53 | } 54 | ], 55 | "position": { 56 | "start": { 57 | "line": 1, 58 | "column": 1, 59 | "offset": 0 60 | }, 61 | "end": { 62 | "line": 6, 63 | "column": 1, 64 | "offset": 34 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /test/fixtures/toml-deep/tree.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "root", 3 | "children": [ 4 | { 5 | "type": "toml", 6 | "value": "title = \"Example\"\n # A comment +++", 7 | "position": { 8 | "start": { 9 | "line": 1, 10 | "column": 1, 11 | "offset": 0 12 | }, 13 | "end": { 14 | "line": 4, 15 | "column": 4, 16 | "offset": 43 17 | } 18 | } 19 | }, 20 | { 21 | "type": "heading", 22 | "depth": 1, 23 | "children": [ 24 | { 25 | "type": "text", 26 | "value": "Deep", 27 | "position": { 28 | "start": { 29 | "line": 6, 30 | "column": 3, 31 | "offset": 47 32 | }, 33 | "end": { 34 | "line": 6, 35 | "column": 7, 36 | "offset": 51 37 | } 38 | } 39 | } 40 | ], 41 | "position": { 42 | "start": { 43 | "line": 6, 44 | "column": 1, 45 | "offset": 45 46 | }, 47 | "end": { 48 | "line": 6, 49 | "column": 7, 50 | "offset": 51 51 | } 52 | } 53 | } 54 | ], 55 | "position": { 56 | "start": { 57 | "line": 1, 58 | "column": 1, 59 | "offset": 0 60 | }, 61 | "end": { 62 | "line": 7, 63 | "column": 1, 64 | "offset": 52 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /test/fixtures/yaml-deep/tree.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "root", 3 | "children": [ 4 | { 5 | "type": "yaml", 6 | "value": "title: post\nobject:\n key: value ---", 7 | "position": { 8 | "start": { 9 | "line": 1, 10 | "column": 1, 11 | "offset": 0 12 | }, 13 | "end": { 14 | "line": 5, 15 | "column": 4, 16 | "offset": 44 17 | } 18 | } 19 | }, 20 | { 21 | "type": "heading", 22 | "depth": 1, 23 | "children": [ 24 | { 25 | "type": "text", 26 | "value": "Deep", 27 | "position": { 28 | "start": { 29 | "line": 7, 30 | "column": 3, 31 | "offset": 48 32 | }, 33 | "end": { 34 | "line": 7, 35 | "column": 7, 36 | "offset": 52 37 | } 38 | } 39 | } 40 | ], 41 | "position": { 42 | "start": { 43 | "line": 7, 44 | "column": 1, 45 | "offset": 46 46 | }, 47 | "end": { 48 | "line": 7, 49 | "column": 7, 50 | "offset": 52 51 | } 52 | } 53 | } 54 | ], 55 | "position": { 56 | "start": { 57 | "line": 1, 58 | "column": 1, 59 | "offset": 0 60 | }, 61 | "end": { 62 | "line": 8, 63 | "column": 1, 64 | "offset": 53 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /test/fixtures/custom-deep/tree.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "root", 3 | "children": [ 4 | { 5 | "type": "jsonml", 6 | "value": "{\n title: \"Custom\",\n // A comment ***\n}", 7 | "position": { 8 | "start": { 9 | "line": 1, 10 | "column": 1, 11 | "offset": 0 12 | }, 13 | "end": { 14 | "line": 6, 15 | "column": 4, 16 | "offset": 49 17 | } 18 | } 19 | }, 20 | { 21 | "type": "heading", 22 | "depth": 1, 23 | "children": [ 24 | { 25 | "type": "text", 26 | "value": "Deep", 27 | "position": { 28 | "start": { 29 | "line": 8, 30 | "column": 3, 31 | "offset": 53 32 | }, 33 | "end": { 34 | "line": 8, 35 | "column": 7, 36 | "offset": 57 37 | } 38 | } 39 | } 40 | ], 41 | "position": { 42 | "start": { 43 | "line": 8, 44 | "column": 1, 45 | "offset": 51 46 | }, 47 | "end": { 48 | "line": 8, 49 | "column": 7, 50 | "offset": 57 51 | } 52 | } 53 | } 54 | ], 55 | "position": { 56 | "start": { 57 | "line": 1, 58 | "column": 1, 59 | "offset": 0 60 | }, 61 | "end": { 62 | "line": 9, 63 | "column": 1, 64 | "offset": 58 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /test/fixtures/custom-fence/tree.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "root", 3 | "children": [ 4 | { 5 | "type": "jsonml", 6 | "value": "{\n title: \"Custom\",\n // A comment ***\n}", 7 | "position": { 8 | "start": { 9 | "line": 1, 10 | "column": 1, 11 | "offset": 0 12 | }, 13 | "end": { 14 | "line": 6, 15 | "column": 4, 16 | "offset": 49 17 | } 18 | } 19 | }, 20 | { 21 | "type": "heading", 22 | "depth": 1, 23 | "children": [ 24 | { 25 | "type": "text", 26 | "value": "Deep", 27 | "position": { 28 | "start": { 29 | "line": 8, 30 | "column": 3, 31 | "offset": 53 32 | }, 33 | "end": { 34 | "line": 8, 35 | "column": 7, 36 | "offset": 57 37 | } 38 | } 39 | } 40 | ], 41 | "position": { 42 | "start": { 43 | "line": 8, 44 | "column": 1, 45 | "offset": 51 46 | }, 47 | "end": { 48 | "line": 8, 49 | "column": 7, 50 | "offset": 57 51 | } 52 | } 53 | } 54 | ], 55 | "position": { 56 | "start": { 57 | "line": 1, 58 | "column": 1, 59 | "offset": 0 60 | }, 61 | "end": { 62 | "line": 9, 63 | "column": 1, 64 | "offset": 58 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /test/fixtures/custom-fence-long/tree.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "root", 3 | "children": [ 4 | { 5 | "type": "jsonml", 6 | "value": "{\n title: \"Custom\",\n // A comment ***\n}", 7 | "position": { 8 | "start": { 9 | "line": 1, 10 | "column": 1, 11 | "offset": 0 12 | }, 13 | "end": { 14 | "line": 6, 15 | "column": 11, 16 | "offset": 63 17 | } 18 | } 19 | }, 20 | { 21 | "type": "heading", 22 | "depth": 1, 23 | "children": [ 24 | { 25 | "type": "text", 26 | "value": "Deep", 27 | "position": { 28 | "start": { 29 | "line": 8, 30 | "column": 3, 31 | "offset": 67 32 | }, 33 | "end": { 34 | "line": 8, 35 | "column": 7, 36 | "offset": 71 37 | } 38 | } 39 | } 40 | ], 41 | "position": { 42 | "start": { 43 | "line": 8, 44 | "column": 1, 45 | "offset": 65 46 | }, 47 | "end": { 48 | "line": 8, 49 | "column": 7, 50 | "offset": 71 51 | } 52 | } 53 | } 54 | ], 55 | "position": { 56 | "start": { 57 | "line": 1, 58 | "column": 1, 59 | "offset": 0 60 | }, 61 | "end": { 62 | "line": 9, 63 | "column": 1, 64 | "offset": 72 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /test/fixtures/custom-fence-openclose/tree.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "root", 3 | "children": [ 4 | { 5 | "type": "jsonml", 6 | "value": "{\n title: \"Custom\",\n // A comment ***\n}", 7 | "position": { 8 | "start": { 9 | "line": 1, 10 | "column": 1, 11 | "offset": 0 12 | }, 13 | "end": { 14 | "line": 6, 15 | "column": 8, 16 | "offset": 56 17 | } 18 | } 19 | }, 20 | { 21 | "type": "heading", 22 | "depth": 1, 23 | "children": [ 24 | { 25 | "type": "text", 26 | "value": "Deep", 27 | "position": { 28 | "start": { 29 | "line": 8, 30 | "column": 3, 31 | "offset": 60 32 | }, 33 | "end": { 34 | "line": 8, 35 | "column": 7, 36 | "offset": 64 37 | } 38 | } 39 | } 40 | ], 41 | "position": { 42 | "start": { 43 | "line": 8, 44 | "column": 1, 45 | "offset": 58 46 | }, 47 | "end": { 48 | "line": 8, 49 | "column": 7, 50 | "offset": 64 51 | } 52 | } 53 | } 54 | ], 55 | "position": { 56 | "start": { 57 | "line": 1, 58 | "column": 1, 59 | "offset": 0 60 | }, 61 | "end": { 62 | "line": 9, 63 | "column": 1, 64 | "offset": 65 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /test/fixtures/custom-fence-short/tree.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "root", 3 | "children": [ 4 | { 5 | "type": "jsonml", 6 | "value": "{\n title: \"Custom\",\n // A comment ***\n}", 7 | "position": { 8 | "start": { 9 | "line": 1, 10 | "column": 1, 11 | "offset": 0 12 | }, 13 | "end": { 14 | "line": 6, 15 | "column": 3, 16 | "offset": 47 17 | } 18 | } 19 | }, 20 | { 21 | "type": "heading", 22 | "depth": 1, 23 | "children": [ 24 | { 25 | "type": "text", 26 | "value": "Deep", 27 | "position": { 28 | "start": { 29 | "line": 8, 30 | "column": 3, 31 | "offset": 51 32 | }, 33 | "end": { 34 | "line": 8, 35 | "column": 7, 36 | "offset": 55 37 | } 38 | } 39 | } 40 | ], 41 | "position": { 42 | "start": { 43 | "line": 8, 44 | "column": 1, 45 | "offset": 49 46 | }, 47 | "end": { 48 | "line": 8, 49 | "column": 7, 50 | "offset": 55 51 | } 52 | } 53 | } 54 | ], 55 | "position": { 56 | "start": { 57 | "line": 1, 58 | "column": 1, 59 | "offset": 0 60 | }, 61 | "end": { 62 | "line": 9, 63 | "column": 1, 64 | "offset": 56 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /test/fixtures/custom-marker-openclose/tree.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "root", 3 | "children": [ 4 | { 5 | "type": "jsonml", 6 | "value": "{\n title: \"Custom\",\n // A comment ***\n}", 7 | "position": { 8 | "start": { 9 | "line": 1, 10 | "column": 1, 11 | "offset": 0 12 | }, 13 | "end": { 14 | "line": 6, 15 | "column": 4, 16 | "offset": 49 17 | } 18 | } 19 | }, 20 | { 21 | "type": "heading", 22 | "depth": 1, 23 | "children": [ 24 | { 25 | "type": "text", 26 | "value": "Deep", 27 | "position": { 28 | "start": { 29 | "line": 8, 30 | "column": 3, 31 | "offset": 53 32 | }, 33 | "end": { 34 | "line": 8, 35 | "column": 7, 36 | "offset": 57 37 | } 38 | } 39 | } 40 | ], 41 | "position": { 42 | "start": { 43 | "line": 8, 44 | "column": 1, 45 | "offset": 51 46 | }, 47 | "end": { 48 | "line": 8, 49 | "column": 7, 50 | "offset": 57 51 | } 52 | } 53 | } 54 | ], 55 | "position": { 56 | "start": { 57 | "line": 1, 58 | "column": 1, 59 | "offset": 0 60 | }, 61 | "end": { 62 | "line": 9, 63 | "column": 1, 64 | "offset": 58 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | /** 5 | * @typedef {import('mdast').Root} Root 6 | * @typedef {import('micromark-extension-frontmatter').Options} Options 7 | * @typedef {import('unified').Processor} Processor 8 | */ 9 | 10 | import { 11 | frontmatterFromMarkdown, 12 | frontmatterToMarkdown 13 | } from 'mdast-util-frontmatter' 14 | import {frontmatter} from 'micromark-extension-frontmatter' 15 | 16 | /** @type {Options} */ 17 | const emptyOptions = 'yaml' 18 | 19 | /** 20 | * Add support for frontmatter. 21 | * 22 | * ###### Notes 23 | * 24 | * Doesn’t parse the data inside them: create your own plugin to do that. 25 | * 26 | * @param {Options | null | undefined} [options='yaml'] 27 | * Configuration (default: `'yaml'`). 28 | * @returns {undefined} 29 | * Nothing. 30 | */ 31 | export default function remarkFrontmatter(options) { 32 | // @ts-expect-error: TS is wrong about `this`. 33 | // eslint-disable-next-line unicorn/no-this-assignment 34 | const self = /** @type {Processor} */ (this) 35 | const settings = options || emptyOptions 36 | const data = self.data() 37 | 38 | const micromarkExtensions = 39 | data.micromarkExtensions || (data.micromarkExtensions = []) 40 | const fromMarkdownExtensions = 41 | data.fromMarkdownExtensions || (data.fromMarkdownExtensions = []) 42 | const toMarkdownExtensions = 43 | data.toMarkdownExtensions || (data.toMarkdownExtensions = []) 44 | 45 | micromarkExtensions.push(frontmatter(settings)) 46 | fromMarkdownExtensions.push(frontmatterFromMarkdown(settings)) 47 | toMarkdownExtensions.push(frontmatterToMarkdown(settings)) 48 | } 49 | -------------------------------------------------------------------------------- /test/fixtures/yaml-unconfigured/tree.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "root", 3 | "children": [ 4 | { 5 | "type": "thematicBreak", 6 | "position": { 7 | "start": { 8 | "line": 1, 9 | "column": 1, 10 | "offset": 0 11 | }, 12 | "end": { 13 | "line": 1, 14 | "column": 4, 15 | "offset": 3 16 | } 17 | } 18 | }, 19 | { 20 | "type": "thematicBreak", 21 | "position": { 22 | "start": { 23 | "line": 2, 24 | "column": 1, 25 | "offset": 4 26 | }, 27 | "end": { 28 | "line": 2, 29 | "column": 4, 30 | "offset": 7 31 | } 32 | } 33 | }, 34 | { 35 | "type": "heading", 36 | "depth": 1, 37 | "children": [ 38 | { 39 | "type": "text", 40 | "value": "Empty", 41 | "position": { 42 | "start": { 43 | "line": 4, 44 | "column": 3, 45 | "offset": 11 46 | }, 47 | "end": { 48 | "line": 4, 49 | "column": 8, 50 | "offset": 16 51 | } 52 | } 53 | } 54 | ], 55 | "position": { 56 | "start": { 57 | "line": 4, 58 | "column": 1, 59 | "offset": 9 60 | }, 61 | "end": { 62 | "line": 4, 63 | "column": 8, 64 | "offset": 16 65 | } 66 | } 67 | } 68 | ], 69 | "position": { 70 | "start": { 71 | "line": 1, 72 | "column": 1, 73 | "offset": 0 74 | }, 75 | "end": { 76 | "line": 5, 77 | "column": 1, 78 | "offset": 17 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /test/fixtures/toml-unconfigured/tree.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "root", 3 | "children": [ 4 | { 5 | "type": "paragraph", 6 | "children": [ 7 | { 8 | "type": "text", 9 | "value": "+++\n+++", 10 | "position": { 11 | "start": { 12 | "line": 1, 13 | "column": 1, 14 | "offset": 0 15 | }, 16 | "end": { 17 | "line": 2, 18 | "column": 4, 19 | "offset": 7 20 | } 21 | } 22 | } 23 | ], 24 | "position": { 25 | "start": { 26 | "line": 1, 27 | "column": 1, 28 | "offset": 0 29 | }, 30 | "end": { 31 | "line": 2, 32 | "column": 4, 33 | "offset": 7 34 | } 35 | } 36 | }, 37 | { 38 | "type": "heading", 39 | "depth": 1, 40 | "children": [ 41 | { 42 | "type": "text", 43 | "value": "Empty", 44 | "position": { 45 | "start": { 46 | "line": 4, 47 | "column": 3, 48 | "offset": 11 49 | }, 50 | "end": { 51 | "line": 4, 52 | "column": 8, 53 | "offset": 16 54 | } 55 | } 56 | } 57 | ], 58 | "position": { 59 | "start": { 60 | "line": 4, 61 | "column": 1, 62 | "offset": 9 63 | }, 64 | "end": { 65 | "line": 4, 66 | "column": 8, 67 | "offset": 16 68 | } 69 | } 70 | } 71 | ], 72 | "position": { 73 | "start": { 74 | "line": 1, 75 | "column": 1, 76 | "offset": 0 77 | }, 78 | "end": { 79 | "line": 5, 80 | "column": 1, 81 | "offset": 17 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /test/fixtures/toml-default/tree.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "root", 3 | "children": [ 4 | { 5 | "type": "paragraph", 6 | "children": [ 7 | { 8 | "type": "text", 9 | "value": "+++\ntitle= \"Title\"\n+++", 10 | "position": { 11 | "start": { 12 | "line": 1, 13 | "column": 1, 14 | "offset": 0 15 | }, 16 | "end": { 17 | "line": 3, 18 | "column": 4, 19 | "offset": 22 20 | } 21 | } 22 | } 23 | ], 24 | "position": { 25 | "start": { 26 | "line": 1, 27 | "column": 1, 28 | "offset": 0 29 | }, 30 | "end": { 31 | "line": 3, 32 | "column": 4, 33 | "offset": 22 34 | } 35 | } 36 | }, 37 | { 38 | "type": "heading", 39 | "depth": 1, 40 | "children": [ 41 | { 42 | "type": "text", 43 | "value": "Default", 44 | "position": { 45 | "start": { 46 | "line": 5, 47 | "column": 3, 48 | "offset": 26 49 | }, 50 | "end": { 51 | "line": 5, 52 | "column": 10, 53 | "offset": 33 54 | } 55 | } 56 | } 57 | ], 58 | "position": { 59 | "start": { 60 | "line": 5, 61 | "column": 1, 62 | "offset": 24 63 | }, 64 | "end": { 65 | "line": 5, 66 | "column": 10, 67 | "offset": 33 68 | } 69 | } 70 | } 71 | ], 72 | "position": { 73 | "start": { 74 | "line": 1, 75 | "column": 1, 76 | "offset": 0 77 | }, 78 | "end": { 79 | "line": 6, 80 | "column": 1, 81 | "offset": 34 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /test/fixtures/toml-advanced/tree.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "root", 3 | "children": [ 4 | { 5 | "type": "toml", 6 | "value": "title = \"TOML Example\"\n\n[owner]\nname = \"Tom Preston-Werner\"\ndob = 1979-05-27T07:32:00-08:00 # First class dates\n\n[database]\nserver = \"192.168.1.1\"\nports = [ 8001, 8001, 8002 ]\nconnection_max = 5000\nenabled = true\n\n[servers]\n\n # Indentation (tabs and/or spaces) is allowed but not required\n [servers.alpha]\n ip = \"10.0.0.1\"\n dc = \"eqdc10\"\n\n [servers.beta]\n ip = \"10.0.0.2\"\n dc = \"eqdc10\"\n\n[clients]\ndata = [ [\"gamma\", \"delta\"], [1, 2] ]\n\n# Line breaks are OK when inside arrays\nhosts = [\n \"alpha\",\n \"omega\"\n]", 7 | "position": { 8 | "start": { 9 | "line": 1, 10 | "column": 1, 11 | "offset": 0 12 | }, 13 | "end": { 14 | "line": 33, 15 | "column": 4, 16 | "offset": 524 17 | } 18 | } 19 | }, 20 | { 21 | "type": "heading", 22 | "depth": 1, 23 | "children": [ 24 | { 25 | "type": "text", 26 | "value": "Big document", 27 | "position": { 28 | "start": { 29 | "line": 35, 30 | "column": 3, 31 | "offset": 528 32 | }, 33 | "end": { 34 | "line": 35, 35 | "column": 15, 36 | "offset": 540 37 | } 38 | } 39 | } 40 | ], 41 | "position": { 42 | "start": { 43 | "line": 35, 44 | "column": 1, 45 | "offset": 526 46 | }, 47 | "end": { 48 | "line": 35, 49 | "column": 15, 50 | "offset": 540 51 | } 52 | } 53 | } 54 | ], 55 | "position": { 56 | "start": { 57 | "line": 1, 58 | "column": 1, 59 | "offset": 0 60 | }, 61 | "end": { 62 | "line": 36, 63 | "column": 1, 64 | "offset": 541 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /test/fixtures/yaml-advanced/tree.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "root", 3 | "children": [ 4 | { 5 | "type": "yaml", 6 | "value": "invoice: 34843\ndate : 2001-01-23\nbill-to: &id001\n given : Chris\n family : Dumars\n address:\n lines: |\n 458 Walkman Dr.\n Suite #292\n city : Royal Oak\n state : MI\n postal : 48046\nship-to: *id001\nproduct:\n - sku : BL394D\n quantity : 4\n description : Basketball\n price : 450.00\n - sku : BL4438H\n quantity : 1\n description : Super Hoop\n price : 2392.00\ntax : 251.42\ntotal: 4443.52\ncomments: >\n Late afternoon is best.\n Backup contact is Nancy\n Billsmer @ 338-4338.", 7 | "position": { 8 | "start": { 9 | "line": 1, 10 | "column": 1, 11 | "offset": 0 12 | }, 13 | "end": { 14 | "line": 30, 15 | "column": 4, 16 | "offset": 614 17 | } 18 | } 19 | }, 20 | { 21 | "type": "heading", 22 | "depth": 1, 23 | "children": [ 24 | { 25 | "type": "text", 26 | "value": "Big document", 27 | "position": { 28 | "start": { 29 | "line": 32, 30 | "column": 3, 31 | "offset": 618 32 | }, 33 | "end": { 34 | "line": 32, 35 | "column": 15, 36 | "offset": 630 37 | } 38 | } 39 | } 40 | ], 41 | "position": { 42 | "start": { 43 | "line": 32, 44 | "column": 1, 45 | "offset": 616 46 | }, 47 | "end": { 48 | "line": 32, 49 | "column": 15, 50 | "offset": 630 51 | } 52 | } 53 | } 54 | ], 55 | "position": { 56 | "start": { 57 | "line": 1, 58 | "column": 1, 59 | "offset": 0 60 | }, 61 | "end": { 62 | "line": 33, 63 | "column": 1, 64 | "offset": 631 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /test/fixtures/core-yaml-delayed/tree.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "root", 3 | "children": [ 4 | { 5 | "type": "thematicBreak", 6 | "position": { 7 | "start": { 8 | "line": 3, 9 | "column": 1, 10 | "offset": 2 11 | }, 12 | "end": { 13 | "line": 3, 14 | "column": 4, 15 | "offset": 5 16 | } 17 | } 18 | }, 19 | { 20 | "type": "heading", 21 | "depth": 2, 22 | "children": [ 23 | { 24 | "type": "text", 25 | "value": "title: post", 26 | "position": { 27 | "start": { 28 | "line": 4, 29 | "column": 1, 30 | "offset": 6 31 | }, 32 | "end": { 33 | "line": 4, 34 | "column": 12, 35 | "offset": 17 36 | } 37 | } 38 | } 39 | ], 40 | "position": { 41 | "start": { 42 | "line": 4, 43 | "column": 1, 44 | "offset": 6 45 | }, 46 | "end": { 47 | "line": 5, 48 | "column": 4, 49 | "offset": 21 50 | } 51 | } 52 | }, 53 | { 54 | "type": "heading", 55 | "depth": 1, 56 | "children": [ 57 | { 58 | "type": "text", 59 | "value": "Later (whitespace before yaml)", 60 | "position": { 61 | "start": { 62 | "line": 7, 63 | "column": 3, 64 | "offset": 25 65 | }, 66 | "end": { 67 | "line": 7, 68 | "column": 33, 69 | "offset": 55 70 | } 71 | } 72 | } 73 | ], 74 | "position": { 75 | "start": { 76 | "line": 7, 77 | "column": 1, 78 | "offset": 23 79 | }, 80 | "end": { 81 | "line": 7, 82 | "column": 33, 83 | "offset": 55 84 | } 85 | } 86 | } 87 | ], 88 | "position": { 89 | "start": { 90 | "line": 1, 91 | "column": 1, 92 | "offset": 0 93 | }, 94 | "end": { 95 | "line": 8, 96 | "column": 1, 97 | "offset": 56 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /test/fixtures/custom-yaml-anywhere/tree.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "root", 3 | "children": [ 4 | { 5 | "type": "heading", 6 | "depth": 1, 7 | "children": [ 8 | { 9 | "type": "text", 10 | "value": "YAML not at the top", 11 | "position": { 12 | "start": { 13 | "line": 1, 14 | "column": 3, 15 | "offset": 2 16 | }, 17 | "end": { 18 | "line": 1, 19 | "column": 22, 20 | "offset": 21 21 | } 22 | } 23 | } 24 | ], 25 | "position": { 26 | "start": { 27 | "line": 1, 28 | "column": 1, 29 | "offset": 0 30 | }, 31 | "end": { 32 | "line": 1, 33 | "column": 22, 34 | "offset": 21 35 | } 36 | } 37 | }, 38 | { 39 | "type": "yaml", 40 | "value": "title: post", 41 | "position": { 42 | "start": { 43 | "line": 3, 44 | "column": 1, 45 | "offset": 23 46 | }, 47 | "end": { 48 | "line": 5, 49 | "column": 4, 50 | "offset": 42 51 | } 52 | } 53 | }, 54 | { 55 | "type": "paragraph", 56 | "children": [ 57 | { 58 | "type": "text", 59 | "value": "some text", 60 | "position": { 61 | "start": { 62 | "line": 7, 63 | "column": 1, 64 | "offset": 44 65 | }, 66 | "end": { 67 | "line": 7, 68 | "column": 10, 69 | "offset": 53 70 | } 71 | } 72 | } 73 | ], 74 | "position": { 75 | "start": { 76 | "line": 7, 77 | "column": 1, 78 | "offset": 44 79 | }, 80 | "end": { 81 | "line": 7, 82 | "column": 10, 83 | "offset": 53 84 | } 85 | } 86 | } 87 | ], 88 | "position": { 89 | "start": { 90 | "line": 1, 91 | "column": 1, 92 | "offset": 0 93 | }, 94 | "end": { 95 | "line": 8, 96 | "column": 1, 97 | "offset": 54 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /test/fixtures/custom-default/tree.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "root", 3 | "children": [ 4 | { 5 | "type": "thematicBreak", 6 | "position": { 7 | "start": { 8 | "line": 1, 9 | "column": 1, 10 | "offset": 0 11 | }, 12 | "end": { 13 | "line": 1, 14 | "column": 4, 15 | "offset": 3 16 | } 17 | } 18 | }, 19 | { 20 | "type": "paragraph", 21 | "children": [ 22 | { 23 | "type": "text", 24 | "value": "{\n\"hello\": \"World!\"\n}", 25 | "position": { 26 | "start": { 27 | "line": 2, 28 | "column": 1, 29 | "offset": 4 30 | }, 31 | "end": { 32 | "line": 4, 33 | "column": 2, 34 | "offset": 27 35 | } 36 | } 37 | } 38 | ], 39 | "position": { 40 | "start": { 41 | "line": 2, 42 | "column": 1, 43 | "offset": 4 44 | }, 45 | "end": { 46 | "line": 4, 47 | "column": 2, 48 | "offset": 27 49 | } 50 | } 51 | }, 52 | { 53 | "type": "thematicBreak", 54 | "position": { 55 | "start": { 56 | "line": 5, 57 | "column": 1, 58 | "offset": 28 59 | }, 60 | "end": { 61 | "line": 5, 62 | "column": 4, 63 | "offset": 31 64 | } 65 | } 66 | }, 67 | { 68 | "type": "heading", 69 | "depth": 1, 70 | "children": [ 71 | { 72 | "type": "text", 73 | "value": "Default", 74 | "position": { 75 | "start": { 76 | "line": 7, 77 | "column": 3, 78 | "offset": 35 79 | }, 80 | "end": { 81 | "line": 7, 82 | "column": 10, 83 | "offset": 42 84 | } 85 | } 86 | } 87 | ], 88 | "position": { 89 | "start": { 90 | "line": 7, 91 | "column": 1, 92 | "offset": 33 93 | }, 94 | "end": { 95 | "line": 7, 96 | "column": 10, 97 | "offset": 42 98 | } 99 | } 100 | } 101 | ], 102 | "position": { 103 | "start": { 104 | "line": 1, 105 | "column": 1, 106 | "offset": 0 107 | }, 108 | "end": { 109 | "line": 8, 110 | "column": 1, 111 | "offset": 43 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "remark-frontmatter", 3 | "version": "5.0.0", 4 | "description": "remark plugin to support frontmatter (yaml, toml, and more)", 5 | "license": "MIT", 6 | "keywords": [ 7 | "frontmatter", 8 | "markdown", 9 | "mdast", 10 | "plugin", 11 | "remark", 12 | "remark-plugin", 13 | "toml", 14 | "unified", 15 | "yaml" 16 | ], 17 | "repository": "remarkjs/remark-frontmatter", 18 | "bugs": "https://github.com/remarkjs/remark-frontmatter/issues", 19 | "funding": { 20 | "type": "opencollective", 21 | "url": "https://opencollective.com/unified" 22 | }, 23 | "author": "Titus Wormer (https://wooorm.com)", 24 | "contributors": [ 25 | "Titus Wormer (https://wooorm.com)", 26 | "Lars Trieloff ", 27 | "Max Kueng " 28 | ], 29 | "sideEffects": false, 30 | "type": "module", 31 | "exports": "./index.js", 32 | "files": [ 33 | "lib/", 34 | "index.d.ts", 35 | "index.js" 36 | ], 37 | "dependencies": { 38 | "@types/mdast": "^4.0.0", 39 | "mdast-util-frontmatter": "^2.0.0", 40 | "micromark-extension-frontmatter": "^2.0.0", 41 | "unified": "^11.0.0" 42 | }, 43 | "devDependencies": { 44 | "@types/node": "^20.0.0", 45 | "c8": "^8.0.0", 46 | "is-hidden": "^2.0.0", 47 | "prettier": "^3.0.0", 48 | "remark": "^15.0.0", 49 | "remark-cli": "^11.0.0", 50 | "remark-preset-wooorm": "^9.0.0", 51 | "type-coverage": "^2.0.0", 52 | "typescript": "^5.0.0", 53 | "vfile": "^6.0.0", 54 | "xo": "^0.56.0" 55 | }, 56 | "scripts": { 57 | "build": "tsc --build --clean && tsc --build && type-coverage", 58 | "format": "remark . --frail --output --quiet && prettier . --log-level warn --write && xo --fix", 59 | "prepack": "npm run build && npm run format", 60 | "test": "npm run build && npm run format && npm run test-coverage", 61 | "test-api": "node --conditions development test/index.js", 62 | "test-coverage": "c8 --100 --reporter lcov npm run test-api" 63 | }, 64 | "prettier": { 65 | "bracketSpacing": false, 66 | "singleQuote": true, 67 | "semi": false, 68 | "tabWidth": 2, 69 | "trailingComma": "none", 70 | "useTabs": false 71 | }, 72 | "remarkConfig": { 73 | "plugins": [ 74 | "remark-preset-wooorm" 75 | ] 76 | }, 77 | "typeCoverage": { 78 | "atLeast": 100, 79 | "detail": true, 80 | "ignoreCatch": true, 81 | "strict": true 82 | }, 83 | "xo": { 84 | "overrides": [ 85 | { 86 | "files": [ 87 | "test/**/*.js" 88 | ], 89 | "rules": { 90 | "no-await-in-loop": "off" 91 | } 92 | } 93 | ], 94 | "prettier": true 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /test/fixtures/core-yaml-not-at-top/tree.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "root", 3 | "children": [ 4 | { 5 | "type": "heading", 6 | "depth": 1, 7 | "children": [ 8 | { 9 | "type": "text", 10 | "value": "Two horizontal rules", 11 | "position": { 12 | "start": { 13 | "line": 1, 14 | "column": 3, 15 | "offset": 2 16 | }, 17 | "end": { 18 | "line": 1, 19 | "column": 23, 20 | "offset": 22 21 | } 22 | } 23 | } 24 | ], 25 | "position": { 26 | "start": { 27 | "line": 1, 28 | "column": 1, 29 | "offset": 0 30 | }, 31 | "end": { 32 | "line": 1, 33 | "column": 23, 34 | "offset": 22 35 | } 36 | } 37 | }, 38 | { 39 | "type": "thematicBreak", 40 | "position": { 41 | "start": { 42 | "line": 3, 43 | "column": 1, 44 | "offset": 24 45 | }, 46 | "end": { 47 | "line": 3, 48 | "column": 4, 49 | "offset": 27 50 | } 51 | } 52 | }, 53 | { 54 | "type": "heading", 55 | "depth": 2, 56 | "children": [ 57 | { 58 | "type": "text", 59 | "value": "A horizontal rule", 60 | "position": { 61 | "start": { 62 | "line": 4, 63 | "column": 1, 64 | "offset": 28 65 | }, 66 | "end": { 67 | "line": 4, 68 | "column": 18, 69 | "offset": 45 70 | } 71 | } 72 | } 73 | ], 74 | "position": { 75 | "start": { 76 | "line": 4, 77 | "column": 1, 78 | "offset": 28 79 | }, 80 | "end": { 81 | "line": 5, 82 | "column": 4, 83 | "offset": 49 84 | } 85 | } 86 | }, 87 | { 88 | "type": "paragraph", 89 | "children": [ 90 | { 91 | "type": "text", 92 | "value": "and another.", 93 | "position": { 94 | "start": { 95 | "line": 7, 96 | "column": 1, 97 | "offset": 51 98 | }, 99 | "end": { 100 | "line": 7, 101 | "column": 13, 102 | "offset": 63 103 | } 104 | } 105 | } 106 | ], 107 | "position": { 108 | "start": { 109 | "line": 7, 110 | "column": 1, 111 | "offset": 51 112 | }, 113 | "end": { 114 | "line": 7, 115 | "column": 13, 116 | "offset": 63 117 | } 118 | } 119 | } 120 | ], 121 | "position": { 122 | "start": { 123 | "line": 1, 124 | "column": 1, 125 | "offset": 0 126 | }, 127 | "end": { 128 | "line": 8, 129 | "column": 1, 130 | "offset": 64 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @typedef {import('mdast').Root} Root 3 | * @typedef {import('remark-frontmatter').Options} Options 4 | */ 5 | 6 | import assert from 'node:assert/strict' 7 | import fs from 'node:fs/promises' 8 | import process from 'node:process' 9 | import test from 'node:test' 10 | import {isHidden} from 'is-hidden' 11 | import {remark} from 'remark' 12 | import remarkFrontmatter from 'remark-frontmatter' 13 | 14 | test('remarkFrontmatter', async function (t) { 15 | await t.test('should expose the public api', async function () { 16 | assert.deepEqual(Object.keys(await import('remark-frontmatter')).sort(), [ 17 | 'default' 18 | ]) 19 | }) 20 | 21 | await t.test('should not throw if not passed options', async function () { 22 | assert.doesNotThrow(function () { 23 | remark().use(remarkFrontmatter).freeze() 24 | }) 25 | }) 26 | 27 | await t.test( 28 | 'should throw if not given a preset or a matter', 29 | async function () { 30 | assert.throws(function () { 31 | // @ts-expect-error: check how invalid input is handled. 32 | remark().use(remarkFrontmatter, [1]).freeze() 33 | }, /^Error: Expected matter to be an object, not `1`/) 34 | } 35 | ) 36 | 37 | await t.test('should throw if given an unknown preset', async function () { 38 | assert.throws(function () { 39 | // @ts-expect-error: check how invalid input is handled. 40 | remark().use(remarkFrontmatter, ['jsonml']).freeze() 41 | }, /^Error: Missing matter definition for `jsonml`/) 42 | }) 43 | 44 | await t.test( 45 | 'should throw if given a matter without `type`', 46 | async function () { 47 | assert.throws(function () { 48 | remark() 49 | // @ts-expect-error: check how invalid input is handled. 50 | .use(remarkFrontmatter, [{marker: '*'}]) 51 | .freeze() 52 | }, /^Error: Missing `type` in matter `{"marker":"\*"}`/) 53 | } 54 | ) 55 | 56 | await t.test( 57 | 'should throw if given a matter without `marker`', 58 | async function () { 59 | assert.throws(function () { 60 | remark() 61 | // @ts-expect-error: check how invalid input is handled. 62 | .use(remarkFrontmatter, [{type: 'jsonml'}]) 63 | .freeze() 64 | }, /^Error: Missing `marker` or `fence` in matter `{"type":"jsonml"}`/) 65 | } 66 | ) 67 | }) 68 | 69 | test('fixtures', async function (t) { 70 | const base = new URL('fixtures/', import.meta.url) 71 | const folders = await fs.readdir(base) 72 | 73 | let index = -1 74 | 75 | while (++index < folders.length) { 76 | const folder = folders[index] 77 | 78 | if (isHidden(folder)) continue 79 | 80 | await t.test(folder, async function () { 81 | const folderUrl = new URL(folder + '/', base) 82 | const inputUrl = new URL('input.md', folderUrl) 83 | const outputUrl = new URL('output.md', folderUrl) 84 | const treeUrl = new URL('tree.json', folderUrl) 85 | const configUrl = new URL('config.json', folderUrl) 86 | 87 | const input = String(await fs.readFile(inputUrl)) 88 | 89 | /** @type {Options | undefined} */ 90 | let config 91 | /** @type {Root} */ 92 | let expected 93 | /** @type {string} */ 94 | let output 95 | 96 | try { 97 | config = JSON.parse(String(await fs.readFile(configUrl))) 98 | } catch {} 99 | 100 | const proc = remark().use(remarkFrontmatter, config) 101 | const actual = proc.parse(input) 102 | 103 | try { 104 | output = String(await fs.readFile(outputUrl)) 105 | } catch { 106 | output = input 107 | } 108 | 109 | try { 110 | if ('UPDATE' in process.env) { 111 | throw new Error('Updating…') 112 | } 113 | 114 | expected = JSON.parse(String(await fs.readFile(treeUrl))) 115 | } catch { 116 | expected = actual 117 | 118 | // New fixture. 119 | await fs.writeFile(treeUrl, JSON.stringify(actual, undefined, 2) + '\n') 120 | } 121 | 122 | assert.deepEqual(actual, expected) 123 | 124 | assert.equal(String(await proc.process(input)), String(output)) 125 | }) 126 | } 127 | }) 128 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # remark-frontmatter 2 | 3 | [![Build][build-badge]][build] 4 | [![Coverage][coverage-badge]][coverage] 5 | [![Downloads][downloads-badge]][downloads] 6 | [![Size][size-badge]][size] 7 | [![Sponsors][sponsors-badge]][collective] 8 | [![Backers][backers-badge]][collective] 9 | [![Chat][chat-badge]][chat] 10 | 11 | **[remark][]** plugin to support frontmatter (YAML, TOML, and more). 12 | 13 | ## Contents 14 | 15 | * [What is this?](#what-is-this) 16 | * [When should I use this?](#when-should-i-use-this) 17 | * [Install](#install) 18 | * [Use](#use) 19 | * [API](#api) 20 | * [`unified().use(remarkFrontmatter[, options])`](#unifieduseremarkfrontmatter-options) 21 | * [`Options`](#options) 22 | * [Examples](#examples) 23 | * [Example: different markers and fences](#example-different-markers-and-fences) 24 | * [Example: frontmatter as metadata](#example-frontmatter-as-metadata) 25 | * [Example: frontmatter in MDX](#example-frontmatter-in-mdx) 26 | * [Authoring](#authoring) 27 | * [HTML](#html) 28 | * [CSS](#css) 29 | * [Syntax](#syntax) 30 | * [Syntax tree](#syntax-tree) 31 | * [Types](#types) 32 | * [Compatibility](#compatibility) 33 | * [Security](#security) 34 | * [Related](#related) 35 | * [Contribute](#contribute) 36 | * [License](#license) 37 | 38 | ## What is this? 39 | 40 | This package is a [unified][] ([remark][]) plugin to add support for YAML, TOML, 41 | and other frontmatter. 42 | 43 | Frontmatter is a metadata format in front of the content. 44 | It’s typically written in YAML and is often used with markdown. 45 | 46 | This plugin follow how GitHub handles frontmatter. 47 | GitHub only supports YAML frontmatter, but this plugin also supports different 48 | flavors (such as TOML). 49 | 50 | ## When should I use this? 51 | 52 | You can use frontmatter when you want authors, that have some markup 53 | experience, to configure where or how the content is displayed or supply 54 | metadata about content, and know that the markdown is only used in places 55 | that support frontmatter. 56 | A good example use case is markdown being rendered by (static) site generators. 57 | 58 | If you *just* want to turn markdown into HTML (with maybe a few extensions such 59 | as frontmatter), we recommend [`micromark`][micromark] with 60 | [`micromark-extension-frontmatter`][micromark-extension-frontmatter] instead. 61 | If you don’t use plugins and want to access the syntax tree, you can use 62 | [`mdast-util-from-markdown`][mdast-util-from-markdown] with 63 | [`mdast-util-frontmatter`][mdast-util-frontmatter]. 64 | 65 | ## Install 66 | 67 | This package is [ESM only][esm]. 68 | In Node.js (version 16+), install with [npm][]: 69 | 70 | ```sh 71 | npm install remark-frontmatter 72 | ``` 73 | 74 | In Deno with [`esm.sh`][esmsh]: 75 | 76 | ```js 77 | import remarkFrontmatter from 'https://esm.sh/remark-frontmatter@5' 78 | ``` 79 | 80 | In browsers with [`esm.sh`][esmsh]: 81 | 82 | ```html 83 | 86 | ``` 87 | 88 | ## Use 89 | 90 | Say our document `example.md` contains: 91 | 92 | ```markdown 93 | +++ 94 | layout = "solar-system" 95 | +++ 96 | 97 | # Jupiter 98 | ``` 99 | 100 | …and our module `example.js` contains: 101 | 102 | ```js 103 | import remarkFrontmatter from 'remark-frontmatter' 104 | import remarkParse from 'remark-parse' 105 | import remarkStringify from 'remark-stringify' 106 | import {unified} from 'unified' 107 | import {read} from 'to-vfile' 108 | 109 | const file = await unified() 110 | .use(remarkParse) 111 | .use(remarkStringify) 112 | .use(remarkFrontmatter, ['yaml', 'toml']) 113 | .use(function () { 114 | return function (tree) { 115 | console.dir(tree) 116 | } 117 | }) 118 | .process(await read('example.md')) 119 | 120 | console.log(String(file)) 121 | ``` 122 | 123 | …then running `node example.js` yields: 124 | 125 | ```js 126 | { 127 | type: 'root', 128 | children: [ 129 | {type: 'toml', value: 'layout = "solar-system"', position: [Object]}, 130 | {type: 'heading', depth: 1, children: [Array], position: [Object]} 131 | ], 132 | position: { 133 | start: {line: 1, column: 1, offset: 0}, 134 | end: {line: 6, column: 1, offset: 43} 135 | } 136 | } 137 | ``` 138 | 139 | ```markdown 140 | +++ 141 | layout = "solar-system" 142 | +++ 143 | 144 | # Jupiter 145 | ``` 146 | 147 | ## API 148 | 149 | This package exports no identifiers. 150 | The default export is [`remarkFrontmatter`][api-remark-frontmatter]. 151 | 152 | ### `unified().use(remarkFrontmatter[, options])` 153 | 154 | Add support for frontmatter. 155 | 156 | ###### Parameters 157 | 158 | * `options` ([`Options`][api-options], default: `'yaml'`) 159 | — configuration 160 | 161 | ###### Returns 162 | 163 | Nothing (`undefined`). 164 | 165 | ###### Notes 166 | 167 | Doesn’t parse the data inside them: 168 | [create your own plugin][unified-create-plugin] to do that. 169 | 170 | ### `Options` 171 | 172 | Configuration (TypeScript type). 173 | 174 | ###### Type 175 | 176 | ```ts 177 | type Options = Array | Matter | Preset 178 | 179 | /** 180 | * Sequence. 181 | * 182 | * Depending on how this structure is used, it reflects a marker or a fence. 183 | */ 184 | export type Info = { 185 | /** 186 | * Closing. 187 | */ 188 | close: string 189 | /** 190 | * Opening. 191 | */ 192 | open: string 193 | } 194 | 195 | /** 196 | * Fence configuration. 197 | */ 198 | type FenceProps = { 199 | /** 200 | * Complete fences. 201 | * 202 | * This can be used when fences contain different characters or lengths 203 | * other than 3. 204 | * Pass `open` and `close` to interface to specify different characters for opening and 205 | * closing fences. 206 | */ 207 | fence: Info | string 208 | /** 209 | * If `fence` is set, `marker` must not be set. 210 | */ 211 | marker?: never 212 | } 213 | 214 | /** 215 | * Marker configuration. 216 | */ 217 | type MarkerProps = { 218 | /** 219 | * Character repeated 3 times, used as complete fences. 220 | * 221 | * For example the character `'-'` will result in `'---'` being used as the 222 | * fence 223 | * Pass `open` and `close` to specify different characters for opening and 224 | * closing fences. 225 | */ 226 | marker: Info | string 227 | /** 228 | * If `marker` is set, `fence` must not be set. 229 | */ 230 | fence?: never 231 | } 232 | 233 | /** 234 | * Fields describing a kind of matter. 235 | * 236 | * > 👉 **Note**: using `anywhere` is a terrible idea. 237 | * > It’s called frontmatter, not matter-in-the-middle or so. 238 | * > This makes your markdown less portable. 239 | * 240 | * > 👉 **Note**: `marker` and `fence` are mutually exclusive. 241 | * > If `marker` is set, `fence` must not be set, and vice versa. 242 | */ 243 | type Matter = (MatterProps & FenceProps) | (MatterProps & MarkerProps) 244 | 245 | /** 246 | * Fields describing a kind of matter. 247 | */ 248 | type MatterProps = { 249 | /** 250 | * Node type to tokenize as. 251 | */ 252 | type: string 253 | /** 254 | * Whether matter can be found anywhere in the document, normally, only matter 255 | * at the start of the document is recognized. 256 | * 257 | * > 👉 **Note**: using this is a terrible idea. 258 | * > It’s called frontmatter, not matter-in-the-middle or so. 259 | * > This makes your markdown less portable. 260 | */ 261 | anywhere?: boolean | null | undefined 262 | } 263 | 264 | /** 265 | * Known name of a frontmatter style. 266 | */ 267 | type Preset = 'toml' | 'yaml' 268 | ``` 269 | 270 | ## Examples 271 | 272 | ### Example: different markers and fences 273 | 274 | Here are a couple of example of different matter objects and what frontmatter 275 | they match. 276 | 277 | To match frontmatter with the same opening and closing fence, namely three of 278 | the same markers, use for example `{type: 'yaml', marker: '-'}`, which matches: 279 | 280 | ```yaml 281 | --- 282 | key: value 283 | --- 284 | ``` 285 | 286 | To match frontmatter with different opening and closing fences, which each use 287 | three different markers, use for example 288 | `{type: 'custom', marker: {open: '<', close: '>'}}`, which matches: 289 | 290 | ```text 291 | <<< 292 | data 293 | >>> 294 | ``` 295 | 296 | To match frontmatter with the same opening and closing fences, which both use 297 | the same custom string, use for example `{type: 'custom', fence: '+=+=+=+'}`, 298 | which matches: 299 | 300 | ```text 301 | +=+=+=+ 302 | data 303 | +=+=+=+ 304 | ``` 305 | 306 | To match frontmatter with different opening and closing fences, which each use 307 | different custom strings, use for example 308 | `{type: 'json', fence: {open: '{', close: '}'}}`, which matches: 309 | 310 | ```json 311 | { 312 | "key": "value" 313 | } 314 | ``` 315 | 316 | ### Example: frontmatter as metadata 317 | 318 | This plugin handles the syntax of frontmatter in markdown. 319 | It does not *parse* that frontmatter as say YAML or TOML and expose it 320 | somewhere. 321 | 322 | In unified, there is a place for metadata about files: 323 | [`file.data`][vfile-file-data]. 324 | For frontmatter specifically, it’s customary to expose parsed data at `file.data.matter`. 325 | 326 | We can make a plugin that does this. 327 | This example uses the utility [`vfile-matter`][vfile-matter], which is specific 328 | to YAML. 329 | To support other data languages, look at this utility for inspiration. 330 | 331 | `my-unified-plugin-handling-yaml-matter.js`: 332 | 333 | ```js 334 | /** 335 | * @typedef {import('unist').Node} Node 336 | * @typedef {import('vfile').VFile} VFile 337 | */ 338 | 339 | import {matter} from 'vfile-matter' 340 | 341 | /** 342 | * Parse YAML frontmatter and expose it at `file.data.matter`. 343 | * 344 | * @returns 345 | * Transform. 346 | */ 347 | export default function myUnifiedPluginHandlingYamlMatter() { 348 | /** 349 | * Transform. 350 | * 351 | * @param {Node} tree 352 | * Tree. 353 | * @param {VFile} file 354 | * File. 355 | * @returns {undefined} 356 | * Nothing. 357 | */ 358 | return function (tree, file) { 359 | matter(file) 360 | } 361 | } 362 | ``` 363 | 364 | …with an example markdown file `example.md`: 365 | 366 | ```markdown 367 | --- 368 | key: value 369 | --- 370 | 371 | # Venus 372 | ``` 373 | 374 | …and using the plugin with an `example.js` containing: 375 | 376 | ```js 377 | import remarkParse from 'remark-parse' 378 | import remarkFrontmatter from 'remark-frontmatter' 379 | import remarkStringify from 'remark-stringify' 380 | import {read} from 'to-vfile' 381 | import {unified} from 'unified' 382 | import myUnifiedPluginHandlingYamlMatter from './my-unified-plugin-handling-yaml-matter.js' 383 | 384 | const file = await unified() 385 | .use(remarkParse) 386 | .use(remarkStringify) 387 | .use(remarkFrontmatter) 388 | .use(myUnifiedPluginHandlingYamlMatter) 389 | .process(await read('example.md')) 390 | 391 | console.log(file.data.matter) // => {key: 'value'} 392 | ``` 393 | 394 | ### Example: frontmatter in MDX 395 | 396 | MDX has the ability to export data from it, where markdown does not. 397 | When authoring MDX, you can write `export` statements and expose arbitrary data 398 | through them. 399 | It is also possible to write frontmatter, and let a plugin turn those into 400 | export statements. 401 | 402 | To automatically turn frontmatter into export statements, use 403 | [`remark-mdx-frontmatter`][remark-mdx-frontmatter]. 404 | 405 | With an `example.mdx` as follows: 406 | 407 | ```mdx 408 | --- 409 | key: value 410 | --- 411 | 412 | # Mars 413 | ``` 414 | 415 | This plugin can be used as follows: 416 | 417 | ```js 418 | import {compile} from '@mdx-js/mdx' 419 | import remarkFrontmatter from 'remark-frontmatter' 420 | import remarkMdxFrontmatter from 'remark-mdx-frontmatter' 421 | import {read, write} from 'to-vfile' 422 | 423 | const file = await compile(await read('example.mdx'), { 424 | remarkPlugins: [remarkFrontmatter, [remarkMdxFrontmatter, {name: 'matter'}]] 425 | }) 426 | file.path = 'output.js' 427 | await write(file) 428 | 429 | const mod = await import('./output.js') 430 | console.log(mod.matter) // => {key: 'value'} 431 | ``` 432 | 433 | ## Authoring 434 | 435 | When authoring markdown with frontmatter, it’s recommended to use YAML 436 | frontmatter if possible. 437 | While YAML has some warts, it works in the most places, so using it guarantees 438 | the highest chance of portability. 439 | 440 | In certain ecosystems, other flavors are widely used. 441 | For example, in the Rust ecosystem, TOML is often used. 442 | In such cases, using TOML is an okay choice. 443 | 444 | When possible, do not use other types of frontmatter, and do not allow 445 | frontmatter anywhere. 446 | 447 | ## HTML 448 | 449 | Frontmatter does not relate to HTML elements. 450 | It is typically stripped, which is what [`remark-rehype`][remark-rehype] does. 451 | 452 | ## CSS 453 | 454 | This package does not relate to CSS. 455 | 456 | ## Syntax 457 | 458 | See [*Syntax* in 459 | `micromark-extension-frontmatter`](https://github.com/micromark/micromark-extension-frontmatter#syntax). 460 | 461 | ## Syntax tree 462 | 463 | See [*Syntax tree* in 464 | `mdast-util-frontmatter`](https://github.com/syntax-tree/mdast-util-frontmatter#syntax-tree). 465 | 466 | ## Types 467 | 468 | This package is fully typed with [TypeScript][]. 469 | It exports the additional type [`Options`][api-options]. 470 | 471 | The YAML node type is supported in `@types/mdast` by default. 472 | To add other node types, register them by adding them to 473 | `FrontmatterContentMap`: 474 | 475 | ```ts 476 | import type {Data, Literal} from 'mdast' 477 | 478 | interface Toml extends Literal { 479 | type: 'toml' 480 | data?: Data 481 | } 482 | 483 | declare module 'mdast' { 484 | interface FrontmatterContentMap { 485 | // Allow using TOML nodes defined by `remark-frontmatter`. 486 | toml: Toml 487 | } 488 | } 489 | ``` 490 | 491 | ## Compatibility 492 | 493 | Projects maintained by the unified collective are compatible with maintained 494 | versions of Node.js. 495 | 496 | When we cut a new major release, we drop support for unmaintained versions of 497 | Node. 498 | This means we try to keep the current release line, `remark-frontmatter@^5`, 499 | compatible with Node.js 16. 500 | 501 | This plugin works with unified 6+ and remark 13+. 502 | 503 | ## Security 504 | 505 | Use of `remark-frontmatter` does not involve **[rehype][]** ([hast][]) or user 506 | content so there are no openings for [cross-site scripting (XSS)][wiki-xss] 507 | attacks. 508 | 509 | ## Related 510 | 511 | * [`remark-yaml-config`](https://github.com/remarkjs/remark-yaml-config) 512 | — configure remark from YAML configuration 513 | * [`remark-gfm`](https://github.com/remarkjs/remark-gfm) 514 | — support GFM (autolink literals, footnotes, strikethrough, tables, 515 | tasklists) 516 | * [`remark-mdx`](https://github.com/mdx-js/mdx/tree/main/packages/remark-mdx) 517 | — support MDX (ESM, JSX, expressions) 518 | * [`remark-directive`](https://github.com/remarkjs/remark-directive) 519 | — support directives 520 | * [`remark-math`](https://github.com/remarkjs/remark-math) 521 | — support math 522 | 523 | ## Contribute 524 | 525 | See [`contributing.md`][contributing] in [`remarkjs/.github`][health] for ways 526 | to get started. 527 | See [`support.md`][support] for ways to get help. 528 | 529 | This project has a [code of conduct][coc]. 530 | By interacting with this repository, organization, or community you agree to 531 | abide by its terms. 532 | 533 | ## License 534 | 535 | [MIT][license] © [Titus Wormer][author] 536 | 537 | 538 | 539 | [build-badge]: https://github.com/remarkjs/remark-frontmatter/workflows/main/badge.svg 540 | 541 | [build]: https://github.com/remarkjs/remark-frontmatter/actions 542 | 543 | [coverage-badge]: https://img.shields.io/codecov/c/github/remarkjs/remark-frontmatter.svg 544 | 545 | [coverage]: https://codecov.io/github/remarkjs/remark-frontmatter 546 | 547 | [downloads-badge]: https://img.shields.io/npm/dm/remark-frontmatter.svg 548 | 549 | [downloads]: https://www.npmjs.com/package/remark-frontmatter 550 | 551 | [size-badge]: https://img.shields.io/bundlejs/size/remark-frontmatter 552 | 553 | [size]: https://bundlejs.com/?q=remark-frontmatter 554 | 555 | [sponsors-badge]: https://opencollective.com/unified/sponsors/badge.svg 556 | 557 | [backers-badge]: https://opencollective.com/unified/backers/badge.svg 558 | 559 | [collective]: https://opencollective.com/unified 560 | 561 | [chat-badge]: https://img.shields.io/badge/chat-discussions-success.svg 562 | 563 | [chat]: https://github.com/remarkjs/remark/discussions 564 | 565 | [npm]: https://docs.npmjs.com/cli/install 566 | 567 | [esm]: https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c 568 | 569 | [esmsh]: https://esm.sh 570 | 571 | [health]: https://github.com/remarkjs/.github 572 | 573 | [contributing]: https://github.com/remarkjs/.github/blob/main/contributing.md 574 | 575 | [support]: https://github.com/remarkjs/.github/blob/main/support.md 576 | 577 | [coc]: https://github.com/remarkjs/.github/blob/main/code-of-conduct.md 578 | 579 | [license]: license 580 | 581 | [author]: https://wooorm.com 582 | 583 | [mdast-util-from-markdown]: https://github.com/syntax-tree/mdast-util-from-markdown 584 | 585 | [mdast-util-frontmatter]: https://github.com/syntax-tree/mdast-util-frontmatter 586 | 587 | [micromark]: https://github.com/micromark/micromark 588 | 589 | [micromark-extension-frontmatter]: https://github.com/micromark/micromark-extension-frontmatter 590 | 591 | [hast]: https://github.com/syntax-tree/hast 592 | 593 | [rehype]: https://github.com/rehypejs/rehype 594 | 595 | [remark]: https://github.com/remarkjs/remark 596 | 597 | [remark-rehype]: https://github.com/remarkjs/remark-rehype 598 | 599 | [remark-mdx-frontmatter]: https://github.com/remcohaszing/remark-mdx-frontmatter 600 | 601 | [typescript]: https://www.typescriptlang.org 602 | 603 | [unified]: https://github.com/unifiedjs/unified 604 | 605 | [unified-create-plugin]: https://unifiedjs.com/learn/guide/create-a-plugin/ 606 | 607 | [vfile-file-data]: https://github.com/vfile/vfile#filedata 608 | 609 | [vfile-matter]: https://github.com/vfile/vfile-matter 610 | 611 | [wiki-xss]: https://en.wikipedia.org/wiki/Cross-site_scripting 612 | 613 | [api-options]: #options 614 | 615 | [api-remark-frontmatter]: #unifieduseremarkfrontmatter-options 616 | --------------------------------------------------------------------------------