kentcdodds / mdx-bundler

🦤 Give me MDX/TSX strings and I'll give you back a component you can render. Supports imports!
MIT License
1.74k stars 75 forks source link

Run script before MDX Bundler runs #74

Open deadcoder0904 opened 3 years ago

deadcoder0904 commented 3 years ago

Relevant code or config

I have a gif-to-png.js script that converts GIFs to PNG in development in the src/_posts directory. As to why I use it, I have written about it here.

TL;DR I'm creating a GIF Player that shows static PNG images when someone pauses a GIF & play button to play the GIF because I hate to see GIF loop while reading text.

gif-to-png.js

const fs = require('fs')
const path = require('path')
const sharp = require('sharp')
const fg = require('fast-glob')

const ROOT_PATH = process.cwd()
const POSTS_PATH = path.join(ROOT_PATH, 'src/_posts')

function* walkSync(dir) {
    const files = fs.readdirSync(dir, { withFileTypes: true })
    for (let i = 0; i < files.length; i++) {
        if (files[i].isDirectory()) {
            yield* walkSync(path.join(dir, files[i].name))
        } else {
            yield path.join(dir, files[i].name)
        }
    }
}

const gifToPng = async () => {
    try {
        for (let [i, file] of [...walkSync(POSTS_PATH)].entries()) {
            const extname = path.extname(file)
            if (extname === '.gif') {
                const dirname = path.dirname(file)
                const png = path.resolve(dirname, path.basename(file).replace('.gif', '.png'))
                await sharp(file).png().toFile(png)
            }
        }
    } catch (e) {
        console.error('Error thrown:', e)
    }
}

gifToPng()

What you did:

This makes a copy of PNG in src/_posts folder appropriately by converting the GIF file.

What happened:

It doesn't copy the PNG file to the appropriate location as mdx-bundler probably runs earlier than this.

Reproduction repository:

https://github.com/deadcoder0904/mdx-bundler-gif-png-error

Problem description:

I just want those PNG files in public/ directory. Is there any way to run the scripts before mdx-bundler?

Suggested solution:

No idea. I did try running the script in public directory instead of src/_posts so it renames there but it didn't work.

Arcath commented 3 years ago

Looking at it this seems like something an esbuild plugin could do. You could then pass the plugin into mdx-bundler so it can generate the file at the same time.

deadcoder0904 commented 3 years ago

@Arcath just to be clear, do I have to write an esbuild-plugin or remark / rehype plugin?

I already converted the code:

import fs from 'fs'
import path from 'path'
import sharp from 'sharp'
import { Plugin } from 'esbuild'

const ROOT_PATH = process.cwd()
const POSTS_PATH = path.join(ROOT_PATH, 'public')

function* walkSync(dir: fs.PathLike): any {
    const files = fs.readdirSync(dir, { withFileTypes: true })
    for (let i = 0; i < files.length; i++) {
        if (files[i].isDirectory()) {
            yield* walkSync(path.join(dir as string, files[i].name))
        } else {
            yield path.join(dir as string, files[i].name)
        }
    }
}

const gifToPng = async () => {
    try {
        for (let [i, file] of [...walkSync(POSTS_PATH)].entries()) {
            const extname = path.extname(file)
            if (extname === '.gif') {
                console.log(file)
                const dirname = path.dirname(file)
                const png = path.resolve(dirname, path.basename(file).replace('.gif', '.png'))
                await sharp(file).png().toFile(png)
            }
        }
    } catch (e) {
        console.error('Error thrown:', e)
    }
}

export const gifToPngPlugin = (): Plugin => ({
    name: 'gifToPng',
    setup(build) {
        build.onLoad({ filter: /\.gif$/ }, async (args) => {
            const fileName = path.basename(args.path, '.gif')
            let contents = await fs.promises.readFile(args.path, 'utf8')
            console.log({ fileName, contents })

            return {
                contents,
                loader: 'file',
            }
        })
    },
})

And I'm trying to write the plugin but getting an error:

❯ npx esbuild .\src\utils\gif-to-png.ts --platform=node --bundle node_modules/sharp/lib/utility.js:7:22: error: No loader is configured for ".node" files: node_modules/sharp/build/Release/sharp.node 7 │ const sharp = require('../build/Release/sharp.node'); ╵ ~~~~~~~~~

Not sure if ESBuild can run extensions with .node?

Arcath commented 3 years ago

it would need to be a esbuild plugin that has a filter of *.gif and then applies your script to it.

deadcoder0904 commented 3 years ago

@Arcath i've already read it. that's where I got this issue from. the issue isn't .gif but of .node as sharp uses that in my code.

see .gif exists in my script but the first line of code is require('sharp') which fails because it internally calls .node. hence, my question, can ESBuild run extensions with .node?

deadcoder0904 commented 3 years ago

oops, found this :)