primers

Template compilation is a word that is familiar to the front end. When we write Vue and React, we use template and TSX and JSX. In essence, these file browsers are unrecognizable, and are compiled to render functions that perform subsequent operations.

How are template files parsed

The following uses vue-CLI and create-react-app as examples. Other packaging tools are not involved. (e.g., vite)

Vue-cli and create-react-app are two scaffolders based on WebPack. There are special loaders to parse TSX, JSX and template.

The create – reate – Babel – loader in the app

Vue – vue of cli – loader

As we can see, different frameworks have different loaders to help parse. But here, the author needs to learn more about the file parsing process of these corresponding Loaders, so it will not expand to explain how these loaders parse these files.

What I want to point out here is that we can try writing a loader to compile our custom files.

Expected result

Here, we can parse our custom file.xxx to define our own template.

I decided to use the.hug file for the definition. To achieve the bottom effect.

<! - template - >
<template>
  <div>
    <ul>
      <li style="color: red;" > name: {{ name }}</li>
      <li> age: {{ age }}</li>
      <li> role: {{ role }}</li>
    </ul>
  </div>
</template>

<script>
export default() = > ({name: 'hug'.age: '18'.role: 'student'
})
</script>

<! Final effect -->
<div>
  <ul>
    <li style="color: red;"> name: hug</li>
    <li> age: 18</li>
    <li> role: student</li>
  </ul>
</div>
Copy the code

Effect:

The template above is somewhat similar to vue, but in reality it is very different, even if the template file looks similar, but nothing else. If you are interested in vUE template compilation, please check out the related information.

So let’s do that.

Coding phase

Project structures,

We have a new project custom-loader

mkdir custom-loader && cd custom-loader
npm init --y
Copy the code

Here you need to install webpack,webpack-cli,webpack-dev-server, etc. Here you directly give package.json configuration.

{
  "name": "custom-loader"."version": "1.0.0"."description": ""."main": "index.js"."scripts": {
    "dev": "webpack-dev-server"."test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": []."author": ""."license": "ISC"."dependencies": {
    "@babel/core": "^ 7.16.0"."babel-loader": "^ 8.2.3"."html-webpack-plugin": "^ 5.5.0"."webpack-dev-server": "^ 4.6.0"
  },
  "devDependencies": {
    "webpack": "^ 5.64.4"."webpack-cli": "^ 4.9.1." "}}Copy the code

At the same time, we need to create a new webpack configuration file and create webpack.config.js in the project directory.

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  mode: 'development'.entry: path.resolve(__dirname, 'src/index.js'),  / / the entry
  output: {
    path: path.resolve(__dirname, 'build'),
    filename: 'app.js'
  },
  module: {
    rules: [
      // Here is matching our custom file to our 'tpl-loader' parse
      {
        test: /\.hug$/,
        use: [
          'babel-loader',
          {
            loader: 'tpl-loader'.options: {
              log: true}}]}]},plugins: [
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, 'index.html')})],devServer: {
    port: 3000
  },
  // Set the path for loader resolution, otherwise you can not find the specified loader
  resolveLoader: {
    modules: [
      'node_modules',
      path.resolve(__dirname, 'loaders')]}}Copy the code

Create new SRC directory,loaders directory, HTML file

// src/index.js
console.log('index.js');
Copy the code
<! DOCTYPEhtml>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
  <title>Document</title>
</head>
<body>
  <div id="app"></div>
</body>
</html>
Copy the code
// loaders/tpl-loader/index.js
function tplLoader(source) {
  return ` export default { log: () => { console.log('hello, world'); }} `;
}
module.exports = tplLoader;
Copy the code

Let’s see if our environment has been built successfully

npm run dev
Copy the code

If the console output is as follows, the console is successfully set up. If there is an error, please also ask readers to solve the problem according to the error.

index.js
Copy the code

Loader to write

At the top, we wrote a simple loader that essentially returns an object with a function log inside it. (Note that the loader currently writes dead, meaning that any resources that go through the loader will essentially return only objects with logs).

function tplLoader(source) {
  return ` export default { log: () => { console.log('hello, world'); }} `;
}
Copy the code

Create a new template file under SRC and import it under SRC /index.js

// src/views/info.hug
<template>
  <div>
    <ul>
      <li style="color: red;" > name: {{ name }}</li>
      <li> age: {{ age }}</li>
      <li> role: {{ role }}</li>
    </ul>
  </div>
</template>

<script>
export default() = > ({name: 'hug'.age: '18'.role: 'student'
})
</script>
Copy the code
// src/index.js
import Hug from './views/info.hug';

console.log(Hug.log());
Copy the code

The browser result is as follows, at this point our loader is in effect.

Then we just need to parse according to the contents of the file, and we can generate different results. We giveloaderThe first argument passed is actually the contents of our file. We can print it outsource

function tplLoader(source) {
    // ... code
    console.log(source);
    // ... code
}
Copy the code

The rest of the work, we just need to analyze the relevant content can be.

The compiler to write

We created a compiler folder and programmed the Compiler functionality.

// loaders/tpl-loader/compiler/const.js
const HTML_TEMPLATE_TAG = 'template';
const SCRIPT_TEMPLATE_TAG = 'script';

module.exports = {
  HTML_TEMPLATE_TAG,
  SCRIPT_TEMPLATE_TAG
}
Copy the code
// loaders/tpl-loader/compiler/index.js
const { HTML_TEMPLATE_TAG, SCRIPT_TEMPLATE_TAG } = require('./const');

/ * * * in the template interpolation to replace the < div > {{data}} < / div > replaceMap = {1} data: * the < div > {{data}} < / div > = > < div > 1 < / div > *@param {*} Template template *@param {*} ReplaceMap data source *@returns * /
function tplReplace(template, replaceMap) {
  return template.replace(/ \ {\ {(. *?) \}\}/g.(node, key) = > {
    return replaceMap[key.trim()];
  });
}

/** * Generates the regular expression *@param {*} tag 
 * @returns * /
function generateTagReg(tag) {
  return new RegExp(` <${tag}>([\\s\\S]*?) < \ /${tag}> `)}GetTemplateFromSource (source, 'template') // fetch <template>.... The contents of </template> *@param {*} source 
 * @param {*} TemplateTag Example: 'template' *@returns string* /
function getTemplateFromSource(source, templateTag) {
  const res = generateTagReg(templateTag).exec(source);
  return res && res.length > 0 ? res[1] : ' ';
}

/** * get the contents of the 'template' tag *@param {*} source 
 * @returns * / 
function getHTMLTemplateFromSource(source) {
  return getTemplateFromSource(source, HTML_TEMPLATE_TAG);
}

/** * Get the contents of the 'script' tag in the template *@param {*} source 
 * @returns * / 
function getScriptTemplateFromSource(source) {
  return getTemplateFromSource(source, SCRIPT_TEMPLATE_TAG);
}

/** * Parse script contents to get data *@param {*} scriptTemplate 
 * @returns * /
function getDataMapFropmScriptTemplate(scriptTemplate) {
  const fnStr = scriptTemplate.replace('export default'.' ');
  return eval(fnStr)();
}

/** * Compile from template *@param {*} source 
 * @returns string* /
function tplCompiler(source) {
  const htmlTemplate = getHTMLTemplateFromSource(source);
  const scriptTemplate = getScriptTemplateFromSource(source);

  const dataMap = getDataMapFropmScriptTemplate(scriptTemplate);
  return tplReplace(htmlTemplate, dataMap).replace(/[\n\t]+/g.' ');
}

module.exports = {
  tplReplace,
  tplCompiler
}
Copy the code

Modify the import file of tPL-loader

const { tplReplace, tplCompiler } = require('.. /utils/index.js');
const { getOptions } = require('loader-utils');

function tplLoader(source) {
  const { log } = getOptions(this);

  const _log = log ? `console.log('compiled the file which is from The ${this.resourcePath}') ` : ' ';

  const content = tplCompiler(source);

  return `
    export default {
      template: '${content}'} `;
}
module.exports = tplLoader;
Copy the code

The compiler above essentially generates content according to the definition of its specification by means of re, string concatenation and replacement, etc. I will not elaborate on the train of thought of Compiler here, but you can actually play to your heart’s content. You can write complier according to your own specifications and rules.

Finally, we are changing our entry file.

import tpl from './views/info.hug';

const oApp = document.querySelector('#app');

oApp.innerHTML = tpl.template;
Copy the code

Restart to start the project.

npm run dev
Copy the code

results

Write in the last

Above we have written a simple loader to implement parsing our custom files. The implementation of compiler is not described here, because I simply use the regular to process such files, which is relatively rough, and actually has no great reference significance.

But what I want to say is that we can think.

  • What can we do about oursCustom file.
  • Some frame scaffoldingloaderThe realization of the idea.
  • We can use our imagination to write something like whatloader.

reference

Write your own “template compiler loader”