Webpack as a front-end build packaging magic, has been indispensable in today’s front-end projects, Webpack provides a core, the core provides a lot of functions out of the box, at the same time it can be extended by loader and plugin. Webpack itself is exquisitely structured, with tapable plug-in architecture and strong expansibility. Numerous loaders or plugins make Webpack very complicated. Common webpack configurations include: devtool, Entry, Output, module, resolve, plugins, externals, etc. This article will introduce how to make only one loader

Project address: github.com/zybingo/md-…

Each loader is actually a Node module that provides a function that receives resources, performs certain processing and outputs to the next loader. Knowing this rationale, we can also write our own loader. Those of you familiar with Webpack will be familiar with the following diagram

As this illustration illustrates, what WebPack does is package various files into files that browsers can recognize, such as the popular Babel-Loader that converts ES6 into ES5, Sass-loader and less-loader are responsible for precompiling sass less into ordinary CSS. Css-loader parses @import and URL. Style-loader wraps CSS with style tags and packages them into JS. There are also file-loader, raw-loader, and so on for files. Different Loaders perform different tasks, and multiple files configured for the same class of files are executed from the bottom up.

In the project development, we will use some Markdown files. For some static pages, such as user agreement pages, we will write a Markdown in the project, use JS to read and parse the file, and finally render it. If we can compile the MD file into JS module during webpack compilation, and introduce it directly into the page, wouldn’t it be more convenient?

Don’t talk too much, just masturbate

Webpack provides reference documents for writing loaders

Webpack.docschina.org/concepts/#l…


There is a library for loader-utils and we make full use of the loader-utils package. It provides many useful tools, but one of the most common is to get the options passed to the Loader. The schema-utils package works with loader-utils to ensure that the loader option is verified with the JSON Schema structure. Here’s a simple example of using both:

import { getOptions } from 'loader-utils';
import validateOptions from 'schema-utils';

const schema = {
  type: 'object',
  properties: {
    test: {
      type: 'string'}}};export default function(source) {
  const options = getOptions(this);

  validateOptions(schema, options, 'Example Loader'); // Apply some transformations to the resource...return `export default ${ JSON.stringify(source) }`;
}
Copy the code

As you can see, all the loader needs to do is convert the resources and output a template string. Finally, the template string is a normal JS module, which can be handled directly by Babel.

  1. First provide the entry to our main function
/ * *read and parser
 * @param {String} sourse
 */
function loader (source) {
  this.cacheable && this.cacheable()
  const callback = this.async()
  const options = Object.assign(defaultOptions, getOptions(this))
  let result = null

  // dosomething
  callback(null, result)
}
Copy the code

2. A normal Markdown file, when parsed in webpack, is entered into the Loader as a string

## This is the title ## ### this is the subtitle ###Copy the code

Here we use CommonMark to parse markdown files

/ * *read and parser
 * @param {String} sourse
 */
export function parser (source) {
  const fileContent = JSON.parse(JSON.stringify(source))
  const reader = new commonmark.Parser()
  const writer = new commonmark.HtmlRenderer()
  return writer.render(reader.parse(fileContent.trim()))
}
Copy the code

The parsed content is the corresponding rich text

<h1> this is the title </h1><h2> this is the subtitle </h2>Copy the code

3. Finally, we construct a normal React component template string for output

function loader (source) {
  this.cacheable && this.cacheable()
  const callback = this.async()
  const options = Object.assign(defaultOptions, getOptions(this))
  let result = null
  if (options.use_raw) {
    result = `
      const raw_content = '${source}'
      export default raw_content
    `
  } else {
    const html = parser(source).trim()
    result = `
      import React from 'react'const Component = props => ( <div {... props}>${processHtml(html)}</div>
      )
      export default {
        html: '${html.replace(/\n/g, '')} ',
        Component
      }
    `
  }
  callback(null, result)
}
Copy the code

Call this.async() to print the template string. This is a React Component that outputs a Component and rich text, so you can render rich text as you like. You may have noticed the processHtml method because there may be a code block in the MD file, and the >< character is escaped when CommonMark parses the code block, so that needs to be handled here. At the same time, the contents of {} in JSX are executed as JS, so the following code reports an error

const MyComponent = () => {
  return (
    <pre>
      <code>
        function calc () {
          let a = 3
          return a
        }
      </code>
    </pre>
  )
}
Copy the code

We need to convert the contents of code to template strings:

const MyComponent = () => {
  return (
    <pre>
      <code>
        {`
          ${function calc () {
            let a = 3
            return a
          }}
        `}
      </code>
    </pre>
  )
}
Copy the code

That’s what processHtml does

/** process raw html
 * @param {String} html
 */
export function processHtml (html) {
  const codeReg = new RegExp('<code class=([\\s\\S]*)>([\\s\\S]*)</code>')
  const code = codeReg.exec(html)
  let newHtml = html
  if (code) {
    newHtml = newHtml.replace(code[2], '{` + code[2] + '`}').replace(/&gt; /g,'>').replace(/&lt; /g,'<')}return newHtml
}
Copy the code

Finally, our loader sends the package. Md-react-loader can be searched on NPM

npm i md-react-loaderCopy the code

Configure in the Webpack of our project

{
  test: /\.(md)$/,
  use: [
    {
      loader: require.resolve('babel-loader'),
      options: {
        presets:[
          "@babel/preset-env"."@babel/preset-react"
        ]
      }
    },
    {
      loader: require.resolve('md-react-loader')}}]Copy the code

Note that the loader output is the React component of es6 syntax, which also needs to be parsed by Babel. After recompiling, we can directly introduce Markdown into the project

import Md from './MyTest.md'
const MyComponent = () => {
  return (
    <Md.Component className="my_own_class"/ >)}exportDefault MyComponent or import Md from'./MyTest.md'
const MyComponent = () => (
  <div
    dangerouslySetInnerHTML={{ __html: Md.html }}
  />
)
export default MyComponent
Copy the code

You can also add your own className definition styles. This loader provides the basic CSS highlighting when md contains code, and also requires you to install prismJS

import Md from './MyTest.md'
import 'prismjs'
import 'md-react-loader/lib/index.css'

const MyComponent = () => <Md.Component className="my_own_class" />
export default MyComponent
Copy the code

How about a Webpack loader?

One more commercial

Github.com/zybingo/md-…

Happy New Year to you all ~ ~