Skip to content
This repository has been archived by the owner on Aug 7, 2023. It is now read-only.

Commit

Permalink
Refactor/asset (#20)
Browse files Browse the repository at this point in the history
* refactor: asset

* chore: add changeset

* test: add some test for svgr and asset
  • Loading branch information
10Derozan authored Jan 4, 2023
1 parent d5a2980 commit d13a9c2
Show file tree
Hide file tree
Showing 17 changed files with 212 additions and 2,976 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@modern-js/libuild-plugin-svgr",
"comment": "transform svg on load hooks",
"type": "none"
}
],
"packageName": "@modern-js/libuild-plugin-svgr"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@modern-js/libuild",
"comment": "refactor asset, support svgr in bundleless",
"type": "none"
}
],
"packageName": "@modern-js/libuild"
}
21 changes: 9 additions & 12 deletions packages/libuild-plugin-svgr/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import fs from 'fs';
import path from 'path';
import { transform, Config } from '@svgr/core';
import { createFilter, CreateFilter } from 'rollup-pluginutils';
import svgo from '@svgr/plugin-svgo';
Expand All @@ -14,32 +13,30 @@ export interface Options extends Config {
const PLUGIN_NAME = 'libuild:svgr';
const SVG_REGEXP = /\.svg$/;

const EXPORT_REGEX = /(module\.exports *= *|export default)/;
export const svgrPlugin = (options: Options = {}): LibuildPlugin => {
const filter = createFilter(options.include || SVG_REGEXP, options.exclude);
return {
name: PLUGIN_NAME,
apply(compiler) {
compiler.hooks.transform.tapPromise(PLUGIN_NAME, async (args) => {
const basename = path.basename(args.path);
const code = `./${basename}`;
if (!filter(args.path)) return args;
compiler.loadSvgr = async (path: string) => {
if (!filter(path)) return;
const loader = 'jsx';
const text = fs.readFileSync(args.path.split('?')[0], 'utf8');
const previousExport = EXPORT_REGEX.test(code) ? code : `export default "${code}"`;
const text = fs.readFileSync(path.split('?')[0], 'utf8');
const jsCode = await transform(text, options, {
filePath: args.path,
filePath: path,
caller: {
name: PLUGIN_NAME,
previousExport,
defaultPlugins: [svgo, jsx],
},
});
return {
code: jsCode,
path: args.path,
contents: jsCode,
loader,
};
};
compiler.hooks.load.tapPromise(PLUGIN_NAME, async (args) => {
const result = await compiler.loadSvgr(args.path);
return result;
});
},
};
Expand Down
2 changes: 2 additions & 0 deletions packages/libuild/src/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,8 @@ export class Libuilder implements ILibuilder {
throw new LibuildError(ErrorCode.RESOLVE_OUT_OF_PLUGIN, 'resolve is not allowed to called out of plugin');
}

async loadSvgr(path: string) {}

private transformContextMap = new Map<string, TransformContext>();

getTransformContext(path: string): TransformContext {
Expand Down
87 changes: 47 additions & 40 deletions packages/libuild/src/plugins/asset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import { basename, join, extname, relative, dirname, win32 } from 'path';
import fs from 'fs';
import { createHash } from 'crypto';
import { resolvePathAndQuery } from '@modern-js/libuild-utils';
import type { Loader } from 'esbuild';
import { LibuildPlugin, ILibuilder, Asset } from '../types';

const IMAGE_REGEXP = /\.png$|\.jpe?g$|\.gif$|\.webp$/;
export const assetExt = [
'.svg',
'.png',
Expand Down Expand Up @@ -34,27 +34,13 @@ export const assetsPlugin = (): LibuildPlugin => {
compiler.hooks.load.tapPromise(pluginName, async (args) => {
if (assetExt.find((ext) => ext === extname(args.path))) {
const { originalFilePath } = resolvePathAndQuery(args.path);
const {
bundle,
outdir,
outbase,
asset: { rebase, limit },
} = compiler.config;
const { bundle, outdir, outbase } = compiler.config;
const rebaseFrom = bundle ? outdir : join(outdir, relative(outbase, dirname(args.path)));
if (rebase) {
const contents = await fs.promises.readFile(originalFilePath);
if (contents.length > limit || !bundle) {
return {
contents,
loader: 'copy',
};
}
}

const contents = await getAssetContents.apply(compiler, [originalFilePath, rebaseFrom]);
const { contents, loader } = await getAssetContents.apply(compiler, [originalFilePath, rebaseFrom, true]);
return {
contents,
loader: 'text',
loader,
};
}
});
Expand Down Expand Up @@ -86,37 +72,58 @@ function encodeSVG(buffer: Buffer) {
*
* @param this Compiler
* @param assetPath Absolute path of the asset
* @param rebaseFrom Absolute path of the file which import asset
* @param rebaseFrom Absolute path of the file which use asset
* @param calledOnLoad called in load hooks
* @returns dataurl or path
*/
export async function getAssetContents(this: ILibuilder, assetPath: string, rebaseFrom?: string) {
export async function getAssetContents(
this: ILibuilder,
assetPath: string,
rebaseFrom: string,
calledOnLoad?: boolean
) {
const fileContent = await fs.promises.readFile(assetPath);
const { bundle } = this.config;
const { name: assetName, limit, rebase, outdir, publicPath } = this.config.asset;
if (fileContent.length <= limit && bundle) {
// inline base64
const mimetype = (await import('mime-types')).default.lookup(assetPath);
const isSVG = mimetype === 'image/svg+xml';
const data = isSVG ? encodeSVG(fileContent) : fileContent.toString('base64');
const encoding = isSVG ? '' : ';base64';
return `data:${mimetype}${encoding},${data}`;
}
const outputFileName = getOutputFileName(assetPath, fileContent, assetName);
const outputFilePath = join(this.config.outdir, outdir, outputFileName);
this.emitAsset(outputFilePath, {
type: 'asset',
fileName: outputFilePath,
contents: fileContent,
originalFileName: assetPath,
});
if (rebaseFrom && rebase) {
const relativePath = relative(rebaseFrom, outputFilePath);
return normalizeSlashes(relativePath.startsWith('..') ? relativePath : `./${relativePath}`);
}
const filePath = `${
const relativePath = relative(rebaseFrom, outputFilePath);
const normalizedRelativePath = normalizeSlashes(relativePath.startsWith('..') ? relativePath : `./${relativePath}`);
const normalizedPublicPath = `${
typeof publicPath === 'function' ? publicPath(assetPath) : publicPath
}${outdir}/${outputFileName}`;
return filePath;
let emitAsset = true;
let contents = normalizedPublicPath;
let loader: Loader = 'text';
if (bundle) {
// inline base64
if (fileContent.length <= limit) {
const mimetype = (await import('mime-types')).default.lookup(assetPath);
const isSVG = mimetype === 'image/svg+xml';
const data = isSVG ? encodeSVG(fileContent) : fileContent.toString('base64');
const encoding = isSVG ? '' : ';base64';
contents = `data:${mimetype}${encoding},${data}`;
loader = 'text';
emitAsset = false;
} else if (rebase) {
contents = calledOnLoad ? fileContent.toString() : normalizedRelativePath;
loader = calledOnLoad ? 'copy' : 'text';
}
} else {
contents = normalizedRelativePath;
}
if (emitAsset) {
this.emitAsset(outputFilePath, {
type: 'asset',
fileName: outputFilePath,
contents: fileContent,
originalFileName: assetPath,
});
}
return {
contents,
loader,
};
}

export function getOutputFileName(filePath: string, content: Buffer, assetName: Required<Asset['name']>): string {
Expand Down
26 changes: 22 additions & 4 deletions packages/libuild/src/plugins/redirect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,17 +65,34 @@ async function redirectImport(
}

// redirect asset path
// import xxx from './xxx.svg';
if (assetExt.filter((ext) => name.endsWith(ext)).length) {
const absPath = resolve(dirname(filePath), name);
const relativeImportPath = await getAssetContents.apply(compiler, [absPath, outputDir]);
str.overwrite(start, end, `${relativeImportPath}`);
const svgrResult = await compiler.loadSvgr(absPath);
if (svgrResult) {
// svgr
const { outdir, outbase } = compiler.config;
const ext = extname(name);
const outputName = `${name.slice(0, -ext.length)}.js`;
const outputFilePath = join(outdir, relative(outbase, dirname(absPath)), basename(outputName));
compiler.emitAsset(outputFilePath, {
type: 'asset',
fileName: outputFilePath,
contents: svgrResult.contents,
originalFileName: absPath,
});
str.overwrite(start, end, outputName);
} else {
// other assets
const { contents: relativeImportPath } = await getAssetContents.apply(compiler, [absPath, outputDir]);
str.overwrite(start, end, `${relativeImportPath}`);
}
return;
}

// redirect style path
// css module
const { originalFilePath, query } = resolvePathAndQuery(name);

// css module
if (query.css_virtual) {
const replacedName = basename(originalFilePath, extname(originalFilePath)).replace('.', '_');
const base = `${replacedName}.css`;
Expand All @@ -91,6 +108,7 @@ async function redirectImport(
const relativeImportPath = normalizeSlashes(`./${base}`);
str.overwrite(start, end, relativeImportPath);
}

// less sass
const ext = extname(name);
if (ext === '.less' || ext === '.sass' || ext === '.scss' || ext === '.css') {
Expand Down
2 changes: 1 addition & 1 deletion packages/libuild/src/plugins/style/postcssUrlPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export const postcssUrlPlugin = (options: { entryPath: string; compilation: ILib
const { css_resolve, outdir, outbase, bundle } = options.compilation.config;
filePath = css_resolve(URL, dirname(options.entryPath));
const rebaseFrom = bundle ? outdir : join(outdir, relative(outbase, dirname(options.entryPath)));
const fileUrl = await getAssetContents.apply(options.compilation, [filePath, rebaseFrom]);
const { contents: fileUrl } = await getAssetContents.apply(options.compilation, [filePath, rebaseFrom]);
(decl as any)[Processed] = true;
return fileUrl;
}
Expand Down
8 changes: 7 additions & 1 deletion packages/libuild/src/types/builder.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Metafile, OnResolveArgs, OnLoadArgs, OnResolveResult, OnLoadResult, BuildOptions } from 'esbuild';
import type { Metafile, OnResolveArgs, OnLoadArgs, OnResolveResult, OnLoadResult, BuildOptions, Loader } from 'esbuild';
import { AsyncSeriesBailHook, AsyncSeriesHook, AsyncSeriesWaterfallHook, SyncHook } from 'tapable';
import { ImportKind } from 'esbuild';
import { CLIConfig, BuildConfig } from './config';
Expand Down Expand Up @@ -74,6 +74,11 @@ export interface BuilderResolveOptions {
skipSideEffects?: boolean;
}

type LoadSvgrResult = {
contents: string;
loader: Loader;
};

export interface IBuilderBase {
build(): Promise<any>;
reBuild(paths: string[]): Promise<void>;
Expand Down Expand Up @@ -101,6 +106,7 @@ export interface ILibuilder {
watchedFiles: Set<string>;
addWatchFile(id: string): void;
resolve(source: string, options?: BuilderResolveOptions): Promise<BuilderResolveResult>;
loadSvgr(path: string): Promise<LoadSvgrResult | void>;
getTransformContext(path: string): ITransformContext;
getSourcemapContext(path: string): ISourcemapContext;
report(error: any): void;
Expand Down
13 changes: 4 additions & 9 deletions tests/fixture/src/assets/__snapshots__/asset.test.ts.snap
Original file line number Diff line number Diff line change
@@ -1,13 +1,8 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`fixture:assets wav file should bundled 1`] = `
"// logo.svg
var logo_default = \\"data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20841.9%20595.3%22%3E%20%20%20%20%3Cg%20fill%3D%22%2361DAFB%22%3E%20%20%20%20%20%20%20%20%3Cpath%20d%3D%22M666.3%20296.5c0-32.5-40.7-63.3-103.1-82.4%2014.4-63.6%208-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6%200%208.3.9%2011.4%202.6%2013.6%207.8%2019.5%2037.5%2014.9%2075.7-1.1%209.4-2.9%2019.3-5.1%2029.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50%2032.6-30.3%2063.2-46.9%2084-46.9V78c-27.5%200-63.5%2019.6-99.9%2053.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7%200%2051.4%2016.5%2084%2046.6-14%2014.7-28%2031.4-41.3%2049.9-22.6%202.4-44%206.1-63.6%2011-2.3-10-4-19.7-5.2-29-4.7-38.2%201.1-67.9%2014.6-75.8%203-1.8%206.9-2.6%2011.5-2.6V78.5c-8.4%200-16%201.8-22.6%205.6-28.1%2016.2-34.4%2066.7-19.9%20130.1-62.2%2019.2-102.7%2049.9-102.7%2082.3%200%2032.5%2040.7%2063.3%20103.1%2082.4-14.4%2063.6-8%20114.2%2020.2%20130.4%206.5%203.8%2014.1%205.6%2022.5%205.6%2027.5%200%2063.5-19.6%2099.9-53.6%2036.4%2033.8%2072.4%2053.2%2099.9%2053.2%208.4%200%2016-1.8%2022.6-5.6%2028.1-16.2%2034.4-66.7%2019.9-130.1%2062-19.1%20102.5-49.9%20102.5-82.3zm-130.2-66.7c-3.7%2012.9-8.3%2026.2-13.5%2039.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4%2014.2%202.1%2027.9%204.7%2041%207.9zm-45.8%20106.5c-7.8%2013.5-15.8%2026.3-24.1%2038.2-14.9%201.3-30%202-45.2%202-15.1%200-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8%206.2-13.4%2013.2-26.8%2020.7-39.9%207.8-13.5%2015.8-26.3%2024.1-38.2%2014.9-1.3%2030-2%2045.2-2%2015.1%200%2030.2.7%2045%201.9%208.3%2011.9%2016.4%2024.6%2024.2%2038%207.6%2013.1%2014.5%2026.4%2020.8%2039.8-6.3%2013.4-13.2%2026.8-20.7%2039.9zm32.3-13c5.4%2013.4%2010%2026.8%2013.8%2039.8-13.1%203.2-26.9%205.9-41.2%208%204.9-7.7%209.8-15.6%2014.4-23.7%204.6-8%208.9-16.1%2013-24.1zM421.2%20430c-9.3-9.6-18.6-20.3-27.8-32%209%20.4%2018.2.7%2027.5.7%209.4%200%2018.7-.2%2027.8-.7-9%2011.7-18.3%2022.4-27.5%2032zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9%203.7-12.9%208.3-26.2%2013.5-39.5%204.1%208%208.4%2016%2013.1%2024%204.7%208%209.5%2015.8%2014.4%2023.4zM420.7%20163c9.3%209.6%2018.6%2020.3%2027.8%2032-9-.4-18.2-.7-27.5-.7-9.4%200-18.7.2-27.8.7%209-11.7%2018.3-22.4%2027.5-32zm-74%2058.9c-4.9%207.7-9.8%2015.6-14.4%2023.7-4.6%208-8.9%2016-13%2024-5.4-13.4-10-26.8-13.8-39.8%2013.1-3.1%2026.9-5.8%2041.2-7.9zm-90.5%20125.2c-35.4-15.1-58.3-34.9-58.3-50.6%200-15.7%2022.9-35.6%2058.3-50.6%208.6-3.7%2018-7%2027.7-10.1%205.7%2019.6%2013.2%2040%2022.5%2060.9-9.2%2020.8-16.6%2041.1-22.2%2060.6-9.9-3.1-19.3-6.5-28-10.2zM310%20490c-13.6-7.8-19.5-37.5-14.9-75.7%201.1-9.4%202.9-19.3%205.1-29.4%2019.6%204.8%2041%208.5%2063.5%2010.9%2013.5%2018.5%2027.5%2035.3%2041.6%2050-32.6%2030.3-63.2%2046.9-84%2046.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7%2038.2-1.1%2067.9-14.6%2075.8-3%201.8-6.9%202.6-11.5%202.6-20.7%200-51.4-16.5-84-46.6%2014-14.7%2028-31.4%2041.3-49.9%2022.6-2.4%2044-6.1%2063.6-11%202.3%2010.1%204.1%2019.8%205.2%2029.1zm38.5-66.7c-8.6%203.7-18%207-27.7%2010.1-5.7-19.6-13.2-40-22.5-60.9%209.2-20.8%2016.6-41.1%2022.2-60.6%209.9%203.1%2019.3%206.5%2028.1%2010.2%2035.4%2015.1%2058.3%2034.9%2058.3%2050.6-.1%2015.7-23%2035.6-58.4%2050.6zM320.8%2078.4z%22%2F%3E%20%20%20%20%20%20%20%20%3Ccircle%20cx%3D%22420.9%22%20cy%3D%22296.5%22%20r%3D%2245.7%22%2F%3E%20%20%20%20%20%20%20%20%3Cpath%20d%3D%22M520.5%2078.1z%22%2F%3E%20%20%20%20%3C%2Fg%3E%3C%2Fsvg%3E\\";
// logo.png
var logo_default2 = \\"assets/logo.aa539dfd.png\\";
// index.ts
console.log(logo_default, logo_default2);
exports[`fixture:assets bundle default, copy asset 1`] = `
"// index.ts
import svg from \\"./assets/logo.PGX3QVVN.svg\\";
console.log(svg);
"
`;
63 changes: 58 additions & 5 deletions tests/fixture/src/assets/asset.test.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,68 @@
import { getLibuilderTest } from '@modern-js/libuild-test-toolkit';
import { expect, getLibuilderTest } from '@modern-js/libuild-test-toolkit';

describe('fixture:assets', () => {
it('wav file should bundled', async () => {
describe('fixture:assets bundle', () => {
it('default, copy asset', async () => {
const bundler = await getLibuilderTest({
root: __dirname,
input: { default: './index.ts' },
});
await bundler.build();
bundler.expectJSOutputMatchSnapshot();
const jsOutput = bundler.getJSOutput();
const jsChunk = Object.values(jsOutput);
expect(jsChunk[0].contents.includes('import')).to.be.true;
});
it('rebase is false, support publicPath', async () => {
const bundler = await getLibuilderTest({
root: __dirname,
asset: {
limit: 14 * 1024,
rebase: false,
publicPath: 'aaa/',
},
input: { publicPath: './index.ts' },
});
await bundler.build();
bundler.expectJSOutputMatchSnapshot();
const jsOutput = bundler.getJSOutput();
const jsChunk = Object.values(jsOutput);
expect(jsChunk[0].contents.includes('aaa/assets')).to.be.true;
});
it('limit asset', async () => {
const bundler = await getLibuilderTest({
root: __dirname,
input: { limit: './index.ts' },
asset: {
limit: 14 * 1024,
},
});
await bundler.build();
const jsOutput = bundler.getJSOutput();
const jsChunk = Object.values(jsOutput);
expect(jsChunk[0].contents.includes('data:image')).to.be.true;
});
it('css url', async () => {
const bundler = await getLibuilderTest({
root: __dirname,
input: { url: './index.less' },
});
await bundler.build();
const cssOutput = bundler.getCSSOutput();
const cssChunk = Object.values(cssOutput);
expect(cssChunk[0].contents.includes(' url(./assets/')).to.be.true;
});
});

describe('fixture:assets no bundle', () => {
it('default ', async () => {
const bundler = await getLibuilderTest({
root: __dirname,
input: { default: './index.ts' },
bundle: false,
outdir: 'dist/bundleless',
outbase: '.',
});
await bundler.build();
const jsOutput = bundler.getJSOutput();
const jsChunk = Object.values(jsOutput);
expect(jsChunk[0].contents.includes('import svg from "./assets')).to.be.true;
});
});
2 changes: 1 addition & 1 deletion tests/fixture/src/assets/index.less
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
.png {
background: url('./logo.png');
background: url('./logo.svg');
}
5 changes: 2 additions & 3 deletions tests/fixture/src/assets/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import svg from './logo.svg';
import png from './logo.png';
import './index.less';
console.log(svg, png);

console.log(svg);
Binary file removed tests/fixture/src/assets/logo.png
Binary file not shown.
Loading

0 comments on commit d13a9c2

Please sign in to comment.