Hello, I’m glad you can click on this blog, this blog is for the Vite experience series of actual combat article, after reading carefully I believe you can also write a Vite plug-in of your own.
Vite is a new front-end build tool that can significantly improve the front-end development experience.
From zero to one, I will complete a Vite: MarkDown plug-in that reads markdown files in the project directory, parses them into HTML, and eventually renders them into a page.
If you haven’t used Vite yet, you can check out my first two articles. I’ve only been playing it for two days. (below)
- First experience of Vite + Vue3 — Vite chapter
- First experience of Vite + Vue3 — Chapter Vue3
This series also takes a look at the Vite source code. Previous articles can be found here:
- Vite source code interpretation series (graphic combination) – local development server
- Vite source interpretation series (graphic and text combination) – construction
- Vite source code interpretation series (graphic combination) – plug-in
Implementation approach
In fact, the implementation of vite plug-in is the Loader + Plugin for WebPack, we will implement markdown plug-in is actually more like the loader part, but will also use some vite plug-in hook functions (such as hot overload).
I need to prepare a plug-in to convert markdown files to HTML. In this case, I’m using Markdown-it, a popular Markdown parser.
Second, I need to recognize the Markdown tag in the code and read the markdown file specified in the tag. This can be done using the fs module of Node plus the re.
Ok, now that we have the implementation ideas sorted out, we are ready to implement the plugin.
Initialize the plug-in directory
We use the NPM init command to initialize the plug-in, named @vitejs/plugin-markdown.
For easy debugging, I created the plugin directory directly in my Vite Demo project.
The repository address of this plugin is @vitejs/ plugin-Markdown. If you are interested, you can download the code directly.
In package.json, we don’t have to worry about setting up the entry file, we can implement our functionality first.
Create test files
Here, we create test files testmd. vue and readme. md in our test project, with the contents and final effect shown below.
Now that we have created the test file, we are ready to explore how to implement it.
Create plug-in entry file —index.ts
Let’s create the plugin entry file — index.ts.
Vite’s plugins support TS, so we’ll write the plugin directly in typescript.
The file contains three attributes: name, Enforce and transform.
- Name: plug-in name;
- Enforce: This plug-in is executed before the plugin-vue plug-in so that it can be parsed directly to the original template file;
- Transform: Code translation, this function is similar to
webpack
的loader
.
export default function markdownPlugin() :Plugin {
return {
// Plug-in name
name: 'vite:markdown'.// This plug-in is executed before the plugin-vue plug-in so that it can be parsed directly to the original template file
enforce: 'pre'.// Code translation, this function is similar to the 'loader' of 'webpack'
transform(code, id, opt){}}}module.exports = markdownPlugin
markdownPlugin['default'] = markdownPlugin
Copy the code
Filter non-target files
Next, we will filter the files, without conversion, the vUE files that are not Vue files and do not use the G-Markdown tag.
Add the following regular code at the beginning of the transform function.
const vueRE = /\.vue$/;
const markdownRE = /\<g-markdown.*\/\>/g;
if(! vueRE.test(id) || ! markdownRE.test(code))return code;
Copy the code
willmarkdown
Tag is replaced byhtml
The text
Next, we will take three steps:
- matching
vue
All in the fileg-markdown
The label - Load the corresponding
markdown
The contents of the file willmarkdown
Convert text to browser-recognizedhtml
The text - will
markdown
Tag is replaced byhtml
Text, introductionstyle
File, output file content
Let’s start by matching all the G-markdown tags in the vue file, using the same re as above:
const mdList = code.match(markdownRE);
Copy the code
Then a traversal is performed on the list of matched tags, and the markdown text inside each tag is read out:
const filePathRE = / (? <=file=("|')).*(? ) / = (" | "); mdList? .forEach(md= > {
// Matches the markdown file directory
const fileRelativePaths = md.match(filePathRE);
if(! fileRelativePaths? .length)return;
// Markdown file relative path
constfileRelativePath = fileRelativePaths! [0];
// Find the current vue directory
const fileDir = path.dirname(id);
// Concatenate the absolute path of the MD file according to the current vue file directory and the relative path of the imported Markdown file
const filePath = path.resolve(fileDir, fileRelativePath);
// Read the contents of the markdown file
const mdText = file.readFileSync(filePath, 'utf-8');
/ /...
});
Copy the code
MdText is the markdown text that we read (figure below)
Next, we need to implement a function to transform this text. We use the markdown-it plug-in mentioned earlier. We create a transformMarkdown function to do this:
const MarkdownIt = require('markdown-it');
const md = new MarkdownIt();
export const transformMarkdown = (mdText: string) :string= > {
// Add a wrapper class named article-content so we can add styles later
return `
<section class='article-content'>
${md.render(mdText)}
</section>
`;
}
Copy the code
Then, we add the conversion function in the above traversal process, and then replace the original label with the converted text, as follows:
mdList? .forEach(md= > {
/ /...
// Read the contents of the markdown file
const mdText = file.readFileSync(filePath, 'utf-8');
// Replace the G-markdown tag with the converted HTML text
transformCode = transformCode.replace(md, transformMarkdown(mdText));
});
Copy the code
Once we have the transformed text, and the page is ready to display, we finally add a gold-digging style file to the transform function, as follows:
transform(code, id, opt) {
/ /...
// Style is a type of text, which is very long
transformCode = `
${transformCode}
<style scoped>
${style}
</style>
`
// Return the converted code
return transformCode;
}
Copy the code
@vitejs/plugin- Markdown combat plugin address
The plug-in
We need to introduce the plug-in in the test project, we can configure it in vite.config.ts, the code implementation is as follows:
In real development, this step should be done early because the plug-in is introduced early so that changes to the plug-in code can be seen in real time.
Some dependencies may be reported missing after the plug-in is introduced, and you need to install them in your test project for debugging (not in production), such as Markdown-it.
import { defineConfig } from 'vite'
import path from 'path';
import vue from '@vitejs/plugin-vue'
import markdown from './plugin-markdown/src/index';
// https://vitejs.dev/config/
export default defineConfig({
resolve: {
alias: {
"@": path.resolve(__dirname, "src"),}},plugins: [
vue(),
// use the @vitejs/plugin-markdown plugin
markdown()
]
});
Copy the code
Then, using the vite command, start our project (don’t forget to introduce the test file testmd. vue in app.vue) and you’ll see something like this. (As shown below)
Configure hot overload
At this point, our plug-in is missing a hot reload feature. Without this feature, modifying the MD file will not trigger hot reloading, requiring a project restart each time.
We need to listen for our MD file in the plugin’s handleHotUpdate hook function, and hot-reload the vue file that depends on that MD file.
Before we do that, we need to store the vue file that introduced the MD file in the transform traversal loop.
Create a map at the top of the plug-in to store dependencies as follows
const mdRelationMap = new Map<string.string> ();Copy the code
The dependencies are then stored in the transform.
mdList? .forEach(md= > {
/ /...
// Concatenate the absolute path of the MD file according to the current vue file directory and the relative path of the imported Markdown file
const mdFilePath = path.resolve(fileDir, fileRelativePath);
// Record the vue file ID to import the current MD file
mdRelationMap.set(mdFilePath, id);
});
Copy the code
HandleHotUpdate = handleHotUpdate = handleHotUpdate;
handleHotUpdate(ctx) {
const { file, server, modules } = ctx;
// Filter non-MD files
if(path.extname(file) ! = ='.md') return;
// Find the vue file importing the MD file
const relationId = mdRelationMap.get(file) as string;
// Find the moduleNode for the vue file
constrelationModule = [...server.moduleGraph.getModulesByFile(relationId)!] [0];
// Send a websocket message for a single file hot reload
server.ws.send({
type: 'update'.updates: [{type: 'js-update'.path: relationModule.file! , acceptedPath: relationModule.file! , timestamp:new Date().getTime()
}
]
});
// Specify the module to be recompiled
return [...modules, relationModule]
},
Copy the code
At this point, we can modify our MD file and see the page updated in real time. (As shown below)
Fun: by the way, about handleHotUpdate processing document content rarely, for server moduleGraph. GetModulesByFile the API or in vite issue found in the code snippet in the inside, If you find relevant document resources, please also share with me, thank you.
At this point, our plug-in development is complete.
Release the plugin
In all of the above steps, we used local debug mode, which can be cumbersome to share.
Next, we build our package and upload it to NPM for everyone to install and experience.
Let’s add the following lines to package.json.
"main": "dist/index.js".// Import file
"scripts": {
// Empty the dist directory and build the files into it
"build": "rimraf dist && run-s build-bundle"."build-bundle": "esbuild src/index.ts --bundle --platform=node --target=node12 --external:@vue/compiler-sfc --external:vue/compiler-sfc --external:vite --outfile=dist/index.js"
},
Copy the code
Then, don’t forget to install rimraf, run-s, and esbuild dependencies. After installing the dependencies, run NPM run build and see our code compiled into the dist directory.
When everything is ready, we can use the NPM publish command to publish our package. (As shown below)
We can then replace the dependencies in vue.config.ts with our built version as follows:
// Because of the local network problem, I can not upload this package, here I directly import the local package, the same as the reference line NPM package
import markdown from './plugin-markdown';
Copy the code
Then we run the project and successfully parse the Markdown file! (As shown below)
summary
That concludes this tutorial.
To get a better grasp of vite plug-in development, it is important to have a clear understanding of the roles and responsibilities of the following lifecycle hooks.
field | instructions | Belongs to |
---|---|---|
name |
The plug-in name | vite 和 rollup Shared |
handleHotUpdate |
Perform custom HMR (module hot replacement) update processing | vite exclusive |
config |
Called before parsing the Vite configuration. Can be customized configuration, will be withvite Basic configurations are merged |
vite exclusive |
configResolved |
Called after the Vite configuration is parsed. You can readvite To perform some operations |
vite exclusive |
configureServer |
Is the hook used to configure the development server. The most common use case is to add custom middleware to an internal CONNECT application. | vite exclusive |
transformIndexHtml |
conversionindex.html Dedicated hooks for. |
vite exclusive |
options |
In the collectionrollup Before the configuration,vite Called when the (local) service is started, and can berollup Merge configurations |
vite 和 rollup Shared |
buildStart |
inrollup In the building,vite Called when the (local) service is started and accessible in this functionrollup The configuration of the |
vite 和 rollup Shared |
resolveId |
Called when parsing a module, and can return a specialresolveId To specify a certainimport Statement loads a specific module |
vite 和 rollup Shared |
load |
Called when a module is parsed and can return a code block to specify aimport Statement loads a specific module |
vite 和 rollup Shared |
transform |
Called when the module is parsed to convert the source code and output the converted result, similar towebpack 的 loader |
vite 和 rollup Shared |
buildEnd |
invite Before local services are closed,rollup Called before output file to directory |
vite 和 rollup Shared |
closeBundle |
invite Before local services are closed,rollup Called before output file to directory |
vite 和 rollup Shared |
If you find a good article or documentation that covers these hook functions in more detail, feel free to share it.
Until this post, the Vite series of 6 issues has come to a successful end. Thank you for your support.
One last thing
If you’ve already seen it, please give it a thumbs up
Your likes are the greatest encouragement to the author, and can also let more people see this article!
If you find this article helpful, please help to light up the star on Github.