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,61All of the components were claimed and born30A new field owner.
  • Within a month, add19A component,60A PR,349Once 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 bywindow.locationTo modify the route, there will be a situation of refreshing the page, andiframeOn the labelsrcThe address is de-initialized when the parent page is rendered and re-initialized if the page is refreshediframeOn the labelsrcAddress. What we want to achieve is that the parent page is only initialized once when renderingiframeOn the labelsrcAddress, subsequent operations,iframeOn the labelsrcThe 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 toASTStructure,ASTGo 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
  • throughSTYLE_REGandSCRIPT_REGIdentify the Markdown documentstyleandscriptThe code.
  • Identify level 3 headings as category headings in the demo.
  • Because of the pureMobileComponent, demo demo is not convenient to show directly in the document, so extracttype === CodeBlockThe code.
  • Because of the supportPCandMobileThe components,CodeBlockThe code in the preview box may not be normal display, so extracttype === htmlThe 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 documentationscriptThe code, the output code has to be writtenscriptCode;
  • The second is yesscriptIn the case of code, this needs to be puthtml,styleandscriptThe code is spliced in;
  • The third is outputMobileNot streaming layout (see the preview boxButtonComponent), butTabStructure layout (see preview boxPullRefreshComponent), 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:

  1. TOP 1-5 in comments (number of comments + number of comments) : 1 badge or 1 Nuggets IP T-shirt
  2. 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