Skip to content

Add @it-service-npm/remark-include to list of plugins#1478

Merged
wooorm merged 1 commit intoremarkjs:mainfrom
IT-Service-NPM:main
Feb 24, 2026
Merged

Add @it-service-npm/remark-include to list of plugins#1478
wooorm merged 1 commit intoremarkjs:mainfrom
IT-Service-NPM:main

Conversation

@sergey-s-betke
Copy link
Contributor

@sergey-s-betke sergey-s-betke commented Feb 17, 2026

Initial checklist

  • I read the support docs
  • I read the contributing guide
  • I agree to follow the code of conduct
  • I searched issues and discussions and couldn’t find anything or linked relevant results below
  • I made sure the docs are up to date
  • I included tests (or that’s not needed)

Description of changes

Updated plugins list.

Added a new plugin, @it-service-npm/remark-include.

With this plugin, you can use ::include{file=./included.md} statements to compose markdown files together.

This plugin is a modern version of remark-import plugin and remark-include plugin, written in Typescript, and compatible with Remark v15.

Relative images and links in the included files will have their paths rewritten to be relative the original document rather than the included file.

An included markdown file will “inherit” the heading levels. If the ::include{file=./included.md} statement happens under Heading 2, for example, any heading 1 in the included file will be “translated” to have header level 3.

@github-actions github-actions bot added 👋 phase/new Post is being triaged automatically 🤞 phase/open Post is being triaged manually and removed 👋 phase/new Post is being triaged automatically labels Feb 17, 2026
@codecov
Copy link

codecov bot commented Feb 17, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (b8f5045) to head (8f4771d).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff            @@
##              main     #1478   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files            6         6           
  Lines          138       138           
=========================================
  Hits           138       138           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@remcohaszing
Copy link
Member

Thanks! I like the idea of your plugin.

I have some feedback:

  • Use a formatter, such as Prettier
  • If you use TypeScript type annotations, you don’t need to add JSDoc types additionally.
  • TypeScript can help you a lot if you allow it to infer types:
    • I recommend to remove redundant type annotations on variable declarations on variable declarations.
    • I recommend to remove redundant function type parameters on callbacks.
    • You likely don’t need unist-util-is. You can check the type property yourself.
    • I can’t think if a situation where you the mdast Node type. In most cases you’re looking for Nodes instead. However, typically you don’t even need that.
  • I recommend to refactor out the ts-runtime-typecheck dependency. What does isDefined(value) do? To most developers, a simple value == null or value === undefined check is instantly recognizable.
  • Instead of making an assertion in a try block, and use an if-statement.
  • Instead of pushing to an array and reversing at the end, you can unshift.
  • Do you really need an asynchronous version if you already have a synchronous version?
  • You don’t need src/index.ts or src/remark-include/index.ts. They only provide unnecessary indirection.
  • You are dual publishing. In environments where require(esm) is supported, you don’t need to do this. Since you use ESM dependencies, this is broken in environments where require(esm) isn’t supported.
    • You don’t need a complicated build tool. You can use tsc to build your package.
  • You don’t need the imports field in package.json
  • You don’t need the types field in package.json
  • You don’t need the main field in package.json, except if you intend to support really old tooling.
  • The exports field in package.json can be simplified to:
    {
      //
      "exports": {
        "package.json": "./package.json",
         ".": {
          "require": "./dist/index.cjs",
          "default": "./dist/index.js"
        }
      }
    }
    And if you remove the CJS build:
    {
      //
      "exports": {
        "package.json": "./package.json",
         ".": "./dist/index.js"
      }
    }
  • I recommend to remove the presets, and only export the plugin.

@sergey-s-betke
Copy link
Contributor Author

sergey-s-betke commented Feb 18, 2026

Thank You, @remcohaszing, for Your feedback!

  • Use a formatter, such as Prettier

I use eslint as linter (eslint.config.mjs file exists). And VSCode extension 'dbaeumer.vscode-eslint', and GitHub action 'Super-Linter' with eslint. Design time formatting - by VSCode extension 'dbaeumer.vscode-eslint' (configuration - eslint.config.mjs). These tools aids in enforcing format and code style guidelines.

updated:

fixed: IT-Service-NPM/remark-include@08f50db

prettier used as ESLint plugin.

@sergey-s-betke
Copy link
Contributor Author

  • If you use TypeScript type annotations, you don’t need to add JSDoc types additionally.

fixed: IT-Service-NPM/remark-include@33864d2

@sergey-s-betke
Copy link
Contributor Author

  • TypeScript can help you a lot if you allow it to infer types:

    • I recommend to remove redundant type annotations on variable declarations on variable declarations.
    • I recommend to remove redundant function type parameters on callbacks.

This is my paranoia...

@sergey-s-betke
Copy link
Contributor Author

  • You likely don’t need unist-util-is. You can check the type property yourself.

fixed: IT-Service-NPM/remark-include@00421e0

@sergey-s-betke
Copy link
Contributor Author

  • I can’t think if a situation where you the mdast Node type. In most cases you’re looking for Nodes instead. However, typically you don’t even need that.

fixed: IT-Service-NPM/remark-include@0fefca3

@sergey-s-betke
Copy link
Contributor Author

sergey-s-betke commented Feb 18, 2026

  • Do you really need an asynchronous version if you already have a synchronous version?

Yes. This plugin processes the included markdown Abstract Syntax Tree (AST) using a configured Processor (this) object. A configured processor might include asynchronous plugins. And I use processor.run in async plugin version, and processor.runSync for sync version.

@sergey-s-betke
Copy link
Contributor Author

  • Instead of pushing to an array and reversing at the end, you can unshift.

fixed: IT-Service-NPM/remark-include@1f6b0f0

@sergey-s-betke
Copy link
Contributor Author

  • I recommend to refactor out the ts-runtime-typecheck dependency. What does isDefined(value) do? To most developers, a simple value == null or value === undefined check is instantly recognizable.
  • Instead of making an assertion in a try block, and use an if-statement.

fix: IT-Service-NPM/remark-include@458bfaf

@sergey-s-betke
Copy link
Contributor Author

  • You don’t need src/index.ts or src/remark-include/index.ts. They only provide unnecessary indirection.

fixed.

@sergey-s-betke
Copy link
Contributor Author

  • You don’t need the imports field in package.json

fixed: IT-Service-NPM/remark-include@e99217e

  • You are dual publishing. In environments where require(esm) is supported, you don’t need to do this. Since you use ESM dependencies, this is broken in environments where require(esm) isn’t supported.

    • You don’t need a complicated build tool. You can use tsc to build your package.

fixed.

@sergey-s-betke
Copy link
Contributor Author

  • You don’t need the types field in package.json

  • You don’t need the main field in package.json, except if you intend to support really old tooling.

  • The exports field in package.json can be simplified to:

    {
      // …
      "exports": {
        "package.json": "./package.json",
         ".": {
          "require": "./dist/index.cjs",
          "default": "./dist/index.js"
        }
      }
    }
    

    And if you remove the CJS build:

    {
      // …
      "exports": {
        "package.json": "./package.json",
         ".": "./dist/index.js"
      }
    }
    

fixed: IT-Service-NPM/remark-include@7f063e1

@sergey-s-betke
Copy link
Contributor Author

  • I recommend to remove the presets, and only export the plugin.

fixed: IT-Service-NPM/remark-include@d994461

There are two plugins:

  • remarkInclude (preferred)
  • remarkIncludeSync.

And this package provides two plugins presets:

  • remarkIncludePreset. This preset contains:

    • remarkInclude
    • remark-directive
  • remarkIncludePresetSync. This preset contains:

    • remarkIncludeSync
    • remark-directive

@sergey-s-betke
Copy link
Contributor Author

sergey-s-betke commented Feb 18, 2026

v4.2.0 released. @remcohaszing, thank You for Your collaboration!

@sergey-s-betke
Copy link
Contributor Author

@remcohaszing, thank you again for your cooperation!

I would greatly appreciate your feedback on version 4.2.0. I have made efforts to address all your comments.

@sergey-s-betke
Copy link
Contributor Author

Use a formatter, such as Prettier

fixed: IT-Service-NPM/remark-include@08f50db

prettier used as ESLint plugin.

@remcohaszing
Copy link
Member

The code doesn’t appear to be formatted with Prettier 🤔 But that’s more of a tip. I strongly recommend using a formatter, but IMO it’s not a requirement to be added to this list.

I see some type casting. I see you use this:

if (['image', 'link', 'definition'].includes(_node.type)) {
  fixIncludedResourcesURL(_node as Resource, mainFile, includedFile);
}

I know that this pattern of ['some', 'static', 'array'].includes() is pretty popular, but it really confuses TypeScript. You can help TypeScript help you by avoiding this pattern, and instead write:

if (_node.type === 'image' || _node.type === 'link' || _node.type ===  'definition') {
  fixIncludedResourcesURL(_node, mainFile, includedFile);
}

Likewise I think you can get rid of the as Root by passing proper type arguments to Processor. But I don’t know what they are from the top of my head.

Anyway, I appreciate how you took the feedback. I think this is good enough to be added to the list.

@sergey-s-betke
Copy link
Contributor Author

@remcohaszing , thank You!

The code doesn’t appear to be formatted with Prettier 🤔 But that’s more of a tip.

I use Prettier, and format source files with prettier plugin for ESLint. But I use another rules set, may be...

import { defineConfig } from 'eslint/config';
import { ESLint } from 'eslint';
import ESLintJs from '@eslint/js';
import ESLintPluginN from 'eslint-plugin-n';
import ESLintPluginTSDoc from 'eslint-plugin-tsdoc';
import ESLintPluginTypescript from 'typescript-eslint';
import ESLintPluginVitest from '@vitest/eslint-plugin';
import ESLintPluginPrettier from 'eslint-plugin-prettier';
import ESLintPluginUnicorn from 'eslint-plugin-unicorn';
import ESLintConfigPrettier from 'eslint-config-prettier';
import ESLintPluginSonarJs from 'eslint-plugin-sonarjs';

export default defineConfig([
  {
    extends: [
      ESLintPluginSonarJs.configs.recommended,
      ESLintJs.configs.recommended,
      // ESLintPluginTypescript.configs.recommendedTypeChecked,
      ...ESLintPluginTypescript.configs.strictTypeChecked,
      ...ESLintPluginTypescript.configs.stylisticTypeChecked,
      ESLintPluginUnicorn.configs.recommended,
      ESLintConfigPrettier
    ],
    plugins: {
      'n': ESLintPluginN,
      'prettier': ESLintPluginPrettier,
      'unicorn': ESLintPluginUnicorn
    },
    rules: {
      'array-bracket-spacing': ['error', 'never'],
      'block-scoped-var': 'error',
      'brace-style': ['error', '1tbs'],
      'camelcase': 'warn',
      'computed-property-spacing': ['error', 'never'],
      'curly': 'error',
      'eol-last': 'error',
      'eqeqeq': ['error', 'smart'],
      'max-depth': ['warn', 3],
      'max-len': ['warn', 80],
      'max-statements': ['warn', 15],
      'new-cap': 'warn',
      'no-extend-native': 'error',
      'no-mixed-spaces-and-tabs': 'error',
      'no-trailing-spaces': 'error',
      'no-unused-vars': 'warn',
      'no-use-before-define': ['error', 'nofunc'],
      'object-curly-spacing': ['error', 'always'],
      'quotes': ['error', 'single', 'avoid-escape'],
      'semi': ['error', 'always'],
      'keyword-spacing': ['error', { 'before': true, 'after': true }],
      'space-unary-ops': 'error',
      'unicorn/no-typeof-undefined': 'off',
      'unicorn/no-this-assignment': 'off',
      '@typescript-eslint/no-this-alias': 'off',
      'sonarjs/no-alphabetical-sort': 'off'
    },
    languageOptions: {
      ecmaVersion: 2022,
      sourceType: 'module',
      parserOptions: {
        projectService: true,
        tsconfigRootDir: import.meta.dirname,
      }
    },
  },
  {
    ignores: [
      '**/*.js',
      '**/*.mjs',
      'node_modules',
      'dist',
      'coverage'
    ]
  },
  {
    files: [
      'src/**/*.ts'
    ],
    plugins: {
      n: ESLintPluginN,
      tsdoc: (ESLintPluginTSDoc as ESLint.Plugin),
      prettier: ESLintPluginPrettier,
      unicorn: ESLintPluginUnicorn

    },
    rules: {
      'no-unused-vars': 'off',
      '@typescript-eslint/no-explicit-any': 'off',
      '@typescript-eslint/no-non-null-assertion': 'off',
      '@typescript-eslint/no-unused-vars': ['error', {
        'argsIgnorePattern': '^_|^(resolve|reject|err)$'
      }],
      'n/no-missing-import': ['error', {
        'ignoreTypeImport': true
      }]
    },
  },
  {
    files: ['test/**/*.test.ts'],
    plugins: {
      n: ESLintPluginN,
      prettier: ESLintPluginPrettier,
      vitest: ESLintPluginVitest
    },
    rules: {
      // ...ESLintPluginVitest.configs.all.rules,
      ...ESLintPluginVitest.configs.recommended.rules,
      'max-statements': 'off'
    },
    settings: {
      vitest: {
        typecheck: true
      }
    },
    languageOptions: {
      globals: {
        ...ESLintPluginVitest.environments.env.globals,
      },
    }
  },
]);

@sergey-s-betke
Copy link
Contributor Author

I see some type casting. I see you use this:

if (['image', 'link', 'definition'].includes(_node.type)) {
  fixIncludedResourcesURL(_node as Resource, mainFile, includedFile);
}

I know that this pattern of ['some', 'static', 'array'].includes() is pretty popular, but it really confuses TypeScript. You can help TypeScript help you by avoiding this pattern, and instead write:

if (_node.type === 'image' || _node.type === 'link' || _node.type ===  'definition') {
  fixIncludedResourcesURL(_node, mainFile, includedFile);
}

fixed: IT-Service-NPM/remark-include@5dd570a

@wooorm wooorm merged commit 6c18384 into remarkjs:main Feb 24, 2026
8 checks passed
@github-actions

This comment has been minimized.

@wooorm wooorm added 📚 area/docs This affects documentation 💪 phase/solved Post is done labels Feb 24, 2026
@wooorm
Copy link
Member

wooorm commented Feb 24, 2026

Thanks! :)

@github-actions github-actions bot removed the 🤞 phase/open Post is being triaged manually label Feb 24, 2026
@sergey-s-betke
Copy link
Contributor Author

Thanks! :)

Thank You!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

📚 area/docs This affects documentation 💪 phase/solved Post is done

Development

Successfully merging this pull request may close these issues.

4 participants