It takes about 15 minutes to read the full text.
On August 3, we synchronized the progress of Vue DevUI in Nuggets, and hoped that more friends could participate in the construction. Soon, many friends joined our Vue DevUI core development team.
- The next day, Lucky gave us a new component of PR.
- Within a week,
61
All of the components were claimed and born30
A new field owner. - Within a month, add
19
A component,60
A PR,349
Once submitted.
Welcome to join us and build a Vue3 open source component library together. If you are interested, you can add the assistant wechat: devui-Official
Here are the warehouse statistics:
The following is the text.
The introduction
If you’ve ever used a mobile component library (such as Vant), you’ll notice a mobile preview on the right side of the site.
In addition, the content of the mobile preview is synchronized with the external component code demo. When switching components, the content of the mobile preview will change accordingly.
How does this work? Let’s take a look at the Vue DevUI component library in practice!
Take a look at the resulting animation:
1 Scheme Selection
Through the analysis of competing products, it is found that mobile preview is all done by embedded IFrame, that is to say
Mobile Preview is a complete site.
Since I want to make a complete website, I came up with the following scheme under the existing engineering structure:
1.1 Initial assumptions
The current document is built through VitePress, and VitePress provides the ability to customize themes. The developed components can be displayed directly in the MD document, so the idea was to create a new mobile path under VitePress to include the required page for mobile preview.
That will have a problem, the preview pages don’t need the top navigation bar and the menu bar on the left, the survey found, vitepress support only customize a theme, although can through the way of judging routing to be hidden in the template at the top of the navigation bar and the menu bar on the left (not verify whether it is feasible to), but given the md, after all, is to write a document, While it is possible to simply show the effects of components, it may not be appropriate to do some complex functions (such as the list of components on the front page of the preview page) in an MD document.
Even if later features are too complex to be done in an MD document, the current design runs the risk of being torn down and redone.
So exclude this option.
1.2 Another better scheme
It is best to start a new mobile preview project with the technology stack aligned with the component library.
In this way, the component effect can be directly previewed in the new project, and the site construction scheme is mature, can do a lot of things, even if the need to do more complex demonstration effect will not be a big problem.
Therefore, this scheme is adopted.
2. Mobile terminal engineering construction
2.1 Is the new project separate or integrated into the component library?
Considering that component library should be a whole project, component development, document output and effect preview should all be under a project, if the mobile terminal preview part is put into a project alone, it will cause maintenance difficulties. So how do you integrate mobile preview into your component library project?
Create a mobile folder in the root directory to store files related to the mobile preview project. According to the configuration of Vite, you also need to create a mobile
<script type="module" src="/mobile/main.ts"></script>
Copy the code
In the development environment, yes
yarn run app:dev
Copy the code
Start, but vite will default to the index. HTML files as the entrance, so the increase in the access path/mobile HTML preview page can access mobile end, complete access path is: http://localhost:3001/mobile.html#/button.
2.2 Optimization of routing Modes
The current component library project contains the project of the service scenario and adopts the History route. If the mobile terminal previews the history route, it conflicts with the service scenario during the development phase. Therefore, the mobile terminal previews the Hash route.
2.3 Packaging Optimization
According to the configuration of vite multi-page application mode, the code of the business scenario and the code of mobile terminal preview were initially packaged together. Later, considering that the codes of the two projects would need to be deployed separately, the packaging configuration was adjusted as follows:
// vite.config.ts
// Business scenario code configuration file, modify the output directory
export default defineConfig({
// Other configuration...
build: {
outDir: 'dist-src'}})Copy the code
// Create vite.config.mobile. Ts in the root directory
// Mobile previews the configuration file, specifying the package entry and output directory
export default defineConfig({
// Other configuration...
build: {
outDir: 'dist-mobile'.rollupOptions: {
input: {
mobile: path.resolve(__dirname, 'mobile.html')}}}})Copy the code
3 Synchronize routes between parent and child pages
3.1 scenario
The configuration of engineering construction is basically completed, and then we need to consider the realization of specific functions.
At present, the official website (represented by the parent page) can be accessed normally through VitePress, and the mobile preview (represented by the child page) can also be accessed normally through vue3+ Vite construction scheme.
So how do the routing states of the two sites stay the same?
That is, the following scenarios need to be met:
- Click the menu of the parent page to modify the route of the child page
- Click the menu of the child page to modify the route of the parent page
- Click the browser forward and back buttons, the parent page routing needs to be modified synchronously
- Click the back button of the child page, and the parent page route needs to be modified synchronously
3.2 Parent-child Page Communication
Since we use iframe scheme, this function involves iframe parent-child communication, which can be divided into two modes: same-domain and cross-domain communication.
In short, co-domain communication is done by accessing the window object of the corresponding domain and then performing operations accordingly.
Cross-domain communication is all about message synchronization through postMessage’s capabilities.
The following two problems exist in the homodomain:
- Due to local development, the two projects were started separately under two ports, and the same-domain communication could not be realized.
- With domain by
window.location
To modify the route, there will be a situation of refreshing the page, andiframe
On the labelsrc
The address is de-initialized when the parent page is rendered and re-initialized if the page is refreshediframe
On the labelsrc
Address. What we want to achieve is that the parent page is only initialized once when renderingiframe
On the labelsrc
Address, subsequent operations,iframe
On the labelsrc
The address does not change, only the internal route changes.
So the scheme of co-domain communication is excluded.
Next is the solution of cross-domain communication ~
Because our requirement is bidirectional synchronization of the parent page routing state, we need to send and receive messages on both parent and child pages.
The parent page sends messages when routing changes through watch monitoring, with the code as follows:
// The parent page sends messages
watch(
() = > route.path,
(newPath) = > {
// Root route, path is '/'; Other routes: '/component/ XXX /'
constpathArr = newPath? .split('/');
const oFrame = document.querySelector('iframe'); oFrame? .contentWindow? .postMessage( {type: 'devui'.value: newPath === '/' ? '/' : pathArr[pathArr.length - 2],},The '*',); });Copy the code
The parent page accepts the message code as follows:
// The parent page accepts the message
window.addEventListener('message'.function (event) {
if (event.data.type && event.data.type === 'devui') {
router.go(event.data.value ? `/components/${event.data.value}/ ` : '/'); }});Copy the code
Subpages also send messages when the route changes:
watch(router.currentRoute, (newPath) = > {
// There are two sources of route changes:
// parent-- affected by parent page, including parent page routing change, preview page synchronization switch; Page rendered for the first time. Trigger a watch
// "------- trigger watch when clicking the menu on the home page of the sub-page
// If the value is parent, the preview page is affected by the parent page. No need to communicate with postMessage again
const routeChangeFromParent =
sessionStorage.getItem('routeChangeFromParent') = = ='parent';
if (routeChangeFromParent) {
sessionStorage.setItem('routeChangeFromParent'.' ');
return;
}
window.parent.postMessage(
{
type: 'devui'.value: getLinkUrl(newPath.path),
},
The '*',); });Copy the code
The sub-page accepts the message code as follows:
window.addEventListener('message'.(event) = > {
if (event.data.type && event.data.type === 'devui') {
sessionStorage.setItem('routeChangeFromParent'.'parent'); router.replace(event.data.value); }});Copy the code
Implementation of child page return to previous page:
Click the back button to perform the browser’s back action, history.back(). At this point, the watch of the parent page monitors the route change and notifies the child page to change the route.
3.3 Problems encountered
1. Route change loop
Because there are two sources of child page routing changes: parent page notification and click the menu of child page, in order to avoid the loop: child notifies the parent of the change, after the parent changes, notify the child of the change again, the child notifies the parent of the change… , marked by sessionStorage to judge interception.
Two, the page can not be displayed
This problem occurs during page rendering.
Third, select the page text, page disorder
This problem occurs when the page text is selected.
Through the analysis of the second and third problems, it was found that the message event was triggered by “accident”. After locating the problem, it was found that some Chrome plug-ins (Augury and Salad search words) triggered the message event, so the logo of Devui was added to filter the source of the Message event.
4 Sub-page generation
The component library supports BOTH PC and Mobile components, and the demo was written into the documentation when it was written.
After the mobile preview function is added, demo must also be displayed in the preview box, and the demo must be consistent with the document.
At this point, you need to maintain two demos.
In order to reduce the workload of later maintenance, the idea was to extract the demo code from the Markdown document through an automated script, and then export it to the mobile preview project.
The automation script needs to support both individual and all component transformations, with all being entered for all transformations.
The entire automation script can be divided into the following steps:
4.1 How do I know which component to convert?
After you enter a command on the cli, you are prompted with the name of the component to be converted. The component name must be the same as the name of the component folder to extract document content from the specified folder.
The command interaction is as follows:
// mobile-cli-index.js
// Create a command using commander
#!/usr/bin/env node
const { Command } = require('commander');
const { create } = require('./generate-mobile-demo');
const { version } = require('.. /package.json');
const program = new Command();
program
.command('create')
.description('Extract demo code from MD, automatically output to mobile folder')
.action(create);
program.parse().version(version);
Copy the code
// mobile-inquirers.js
// Provide interaction via inquirer
exports.inputComponentName = () = > ({
name: 'name'.type: 'input'.message:
'(Required) Please enter the component name, extract the demo code from this component MD into Mobile, only mobile components are supported, all means convert all mobile components:'.validate: (value) = > {
if (value.trim() === ' ') {
return 'Component name is mandatory';
}
return true; }});Copy the code
// generate-mobile-demo.js
// Provides the action to be performed after the command is entered
const inquirer = require('inquirer');
const { inputComponentName } = require('./mobile-inquirers');
exports.create = async() = > {const { name } = await inquirer.prompt([inputComponentName()]);
if(! validateComponentName(name.toLocaleLowerCase())) {console.log('This component does not support Mobile');
process.exit(0);
}
if (name === 'all') {
// Convert all Mobile components through traversal
mobileComponents.forEach((value, key) = > {
reset();
generateSingleComponent(value, key);
});
} else {
// Single component conversiongenerateSingleComponent(mobileComponents.get(name), name); }};Copy the code
4.2 Extracting Markdown Content
Read the contents of the document with node’s ability as follows:
const mdFileContent = fs.readFileSync(
path.resolve(__dirname, `.. /docs/components/${componentName}/index.md`),
'utf8',);Copy the code
4.3 How to extract the desired information from Markdown content?
At present, two schemes can be realized:
- One is to slice a string with a re.
- The alternative is to convert the Markdown document to
AST
Structure,AST
Go through and get the data you want.
Because AST conversion is more accurate and rigorous, with clear structured information, more flexible;
Regular expressions, on the other hand, require too many boundary cases to effectively analyze context.
Therefore, the AST scheme is adopted.
Borrow the structure of the @textLint/Markdown-to-ast plugin to convert markdown documents to ast:
const mdParser = require('@textlint/markdown-to-ast').parse;
const mdAST = mdParser(mdFileContent);
Copy the code
The structure of the AST is as follows (only part of it is captured) :
By iterating through the AST to extract the code, the style, script, tertiary title and the HTML or CodeBlock immediately following in the document are the data we need. The specific extraction process is as follows:
mdAST.children.forEach(({ type, depth, lang, raw }) = > {
if (STYLE_REG.test(raw)) {
styleStr = raw;
} else if (SCRIPT_REG.test(raw)) {
scriptStr = raw;
} else if (type === 'Header' && depth === 3) {
needPickHtml = true;
let title = raw.replace('# # #'.' ');
isFlow
? (htmlStr += `<div class="demo-block__title">${title}</div>`)
: tabTitleArr.push(title);
} else if (isPureMobile) {
if (needPickHtml && type === 'CodeBlock' && lang && lang === 'html') {
let content = raw.replace('```html'.' ').replace('` ` `'.' ');
isFlow ? (htmlStr += content) : tabContentArr.push(content);
needPickHtml = false; }}else {
if (needPickHtml && type === 'Html') {
isFlow ? (htmlStr += raw) : tabContentArr.push(raw);
needPickHtml = false; }}});Copy the code
- through
STYLE_REG
andSCRIPT_REG
Identify the Markdown documentstyle
andscript
The code. - Identify level 3 headings as category headings in the demo.
- Because of the pure
Mobile
Component, demo demo is not convenient to show directly in the document, so extracttype === CodeBlock
The code. - Because of the support
PC
andMobile
The components,CodeBlock
The code in the preview box may not be normal display, so extracttype === html
The code.
4.4 Concatenating output Strings
After extracting HTML, style, and Script code, the next step is to concatenate strings.
There are three cases of concatenation of strings:
- One is not in the Markdown documentation
script
The code, the output code has to be writtenscript
Code; - The second is yes
script
In the case of code, this needs to be puthtml
,style
andscript
The code is spliced in; - The third is output
Mobile
Not streaming layout (see the preview boxButton
Component), butTab
Structure layout (see preview boxPullRefresh
Component), which requires processing of concatenated strings.
The specific splicing will not be described too much, but the code for the second case is shown below:
function createTemplate() {
return `${scriptStr}
<template>
<div class="component-content">
${htmlStr}
</div>
</template>
${styleStr}
`
}
Copy the code
Once the string concatenation is complete, it is output to a file using Node’s ability. This is relatively simple, not to do too much description, directly paste the code:
let fileStr = createTemplate()
fs.outputFile(path.resolve(componentDir, 'index.vue'), fileStr, 'utf8');
Copy the code
4.5 Previewing the Home page and Routing table on mobile Devices
Mobile terminal preview page is automatically generated, same as the document on the left side of the menu bar is according to the docs/vitepress/config/sidebar. The configuration of ts file generated, so the Mobile end preview of the home page is read from the file configuration generated, just need to do a filter, only render the Mobile component.
Mobile terminal preview engineering routing table is also by reading the docs /. Vitepress/config/sidebar. The configuration of ts file generated, through the form of a string concatenation output to the mobile route/index. The ts file.
5 subtotal
Mobile preview is a complete website that is embedded in the component library’s official website via iframe and then synchronizes parent and child page routing via postMessage capabilities.
The preview page is automatically generated by extracting the demo code from the Markdown document in the way of generating AST. The home page and routing table of the mobile preview are also automatically generated according to the menu bar configuration file of the component library.
This way, you only need to maintain the menu bar and demo code in the Markdown document, reducing the maintenance effort.
Wen/DevUI line
Related articles recommended
Xi Big pu Ben! DevUI 1000 star
Vue DevUI has 11 new members ~ π₯³π
Vue DevUI already has 10 component members ~ π₯³π
Let’s build the Vue DevUI project together! π₯³
This article is participating in the gold Digging Gift around π, welcome to comment on this article and Vue DevUI component library, we will be based on the popularity of the comments (number of comments + number of comprehensive data) lucky draw, specific rules are as follows:
- TOP 1-5 in comments (number of comments + number of comments) : 1 badge or 1 Nuggets IP T-shirt
- Randomly select two users in the comments area, each send a gold digging badge
Comments can be on this article, the Vue DevUI component library itself, or comments and suggestions for DevUI