Skip to content

Commit ee28dc0

Browse files
authored
feat(): load images with imports (#1565)
1 parent 0830959 commit ee28dc0

12 files changed

Lines changed: 119 additions & 9 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
2+
declare module '*.svg' {
3+
const src: string;
4+
export default src;
5+
}

scripts/packages/internal/index.d.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
/// <reference path="./images.d.ts" />
2+
13
export {
24
CompilerCtx,
35
ComponentCompilerMeta,
@@ -12,4 +14,3 @@ export {
1214
PrintLine
1315
} from '../dist/declarations';
1416
export * from '../dist/declarations/docs';
15-

src/compiler/app-core/bundle-app-core.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { stencilBuildConditionalsPlugin } from '../rollup-plugins/stencil-build-
99
import { stencilClientPlugin } from '../rollup-plugins/stencil-client';
1010
import { loaderPlugin } from '../rollup-plugins/loader';
1111
import { stencilExternalRuntimePlugin } from '../rollup-plugins/stencil-external-runtime';
12-
12+
import { imagePlugin } from '../rollup-plugins/image-plugin';
1313

1414
export async function bundleApp(config: d.Config, compilerCtx: d.CompilerCtx, buildCtx: d.BuildCtx, build: d.Build, bundleAppOptions: d.BundleAppOptions) {
1515
const external = bundleAppOptions.skipDeps
@@ -45,6 +45,7 @@ export async function bundleApp(config: d.Config, compilerCtx: d.CompilerCtx, bu
4545
...config.commonjs
4646
}),
4747
bundleJson(config),
48+
imagePlugin(config, buildCtx),
4849
inMemoryFsRead(config, compilerCtx),
4950
config.sys.rollup.plugins.replace({
5051
'process.env.NODE_ENV': config.devMode ? '"development"' : '"production"'

src/compiler/config/validate-outputs-dist.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,11 @@ export function validateOutputTargetDist(config: d.Config) {
6565
dir: outputTarget.dir,
6666
collectionDir: outputTarget.collectionDir,
6767
typesDir: outputTarget.typesDir,
68-
copy: outputTarget.copy
68+
copy: [
69+
...outputTarget.copy,
70+
{ src: '**/*.svg' },
71+
{ src: '**/*.js' }
72+
]
6973
});
7074

7175
const namespace = config.fsNamespace || 'app';
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import * as d from '../../declarations';
2+
import { buildWarn, normalizePath } from '@utils';
3+
import { Plugin } from 'rollup';
4+
5+
const mimeTypes: any = {
6+
'.svg': 'image/svg+xml',
7+
};
8+
9+
export function imagePlugin(config: d.Config, buildCtx: d.BuildCtx): Plugin {
10+
11+
return {
12+
name: 'image',
13+
14+
async load(id) {
15+
if (/\0/.test(id)) {
16+
return null;
17+
}
18+
19+
id = normalizePath(id);
20+
const mime = mimeTypes[config.sys.path.extname(id)];
21+
if (!mime) {
22+
return null;
23+
}
24+
25+
try {
26+
const data = await config.sys.fs.readFile(id, 'base64');
27+
if (config.devMode && data.length > MAX_IMAGE_SIZE) {
28+
const warn = buildWarn(buildCtx.diagnostics);
29+
warn.messageText = 'Importing big images will bloat your bundle, please use assets instead.';
30+
warn.absFilePath = id;
31+
}
32+
return `const img = 'data:${mime};base64,${data}'; export default img;`;
33+
} catch (e) {}
34+
return null;
35+
}
36+
};
37+
}
38+
39+
const MAX_IMAGE_SIZE = 4 * 1024; // 4KiB

src/declarations/file-system.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ export interface FileSystem {
2121
mkdirSync(dirPath: string): void;
2222
readdir(dirPath: string): Promise<string[]>;
2323
readdirSync(dirPath: string): string[];
24-
readFile(filePath: string): Promise<string>;
25-
readFileSync(filePath: string): string;
24+
readFile(filePath: string, format?: string): Promise<string>;
25+
readFileSync(filePath: string, format?: string): string;
2626
rmdir(dirPath: string): Promise<void>;
2727
stat(path: string): Promise<FsStats>;
2828
statSync(path: string): FsStats;

src/sys/node/node-fs.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -119,9 +119,9 @@ export class NodeFs implements d.FileSystem {
119119
return fs.readdirSync(dirPath);
120120
}
121121

122-
readFile(filePath: string) {
122+
readFile(filePath: string, format = 'utf8') {
123123
return new Promise<string>((resolve, reject) => {
124-
fs.readFile(filePath, 'utf8', (err: any, content: any) => {
124+
fs.readFile(filePath, format, (err: any, content: any) => {
125125
if (err) {
126126
reject(err);
127127
} else {
@@ -141,8 +141,8 @@ export class NodeFs implements d.FileSystem {
141141
return fs.existsSync(filePath);
142142
}
143143

144-
readFileSync(filePath: string) {
145-
return fs.readFileSync(filePath, 'utf8');
144+
readFileSync(filePath: string, format = 'utf8') {
145+
return fs.readFileSync(filePath, format);
146146
}
147147

148148
rmdir(dirPath: string) {

test/karma/test-app/components.d.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ export namespace Components {
6565
interface ExternalImportB {}
6666
interface ExternalImportC {}
6767
interface FactoryJsx {}
68+
interface ImageImport {}
6869
interface InitCssRoot {}
6970
interface InputBasicRoot {
7071
'value'?: string;
@@ -226,6 +227,7 @@ declare namespace LocalJSX {
226227
interface ExternalImportB extends JSXBase.HTMLAttributes {}
227228
interface ExternalImportC extends JSXBase.HTMLAttributes {}
228229
interface FactoryJsx extends JSXBase.HTMLAttributes {}
230+
interface ImageImport extends JSXBase.HTMLAttributes {}
229231
interface InitCssRoot extends JSXBase.HTMLAttributes {}
230232
interface InputBasicRoot extends JSXBase.HTMLAttributes {
231233
'value'?: string;
@@ -365,6 +367,7 @@ declare namespace LocalJSX {
365367
'external-import-b': ExternalImportB;
366368
'external-import-c': ExternalImportC;
367369
'factory-jsx': FactoryJsx;
370+
'image-import': ImageImport;
368371
'init-css-root': InitCssRoot;
369372
'input-basic-root': InputBasicRoot;
370373
'key-reorder': KeyReorder;
@@ -586,6 +589,12 @@ declare global {
586589
new (): HTMLFactoryJsxElement;
587590
};
588591

592+
interface HTMLImageImportElement extends Components.ImageImport, HTMLStencilElement {}
593+
var HTMLImageImportElement: {
594+
prototype: HTMLImageImportElement;
595+
new (): HTMLImageImportElement;
596+
};
597+
589598
interface HTMLInitCssRootElement extends Components.InitCssRoot, HTMLStencilElement {}
590599
var HTMLInitCssRootElement: {
591600
prototype: HTMLInitCssRootElement;
@@ -971,6 +980,7 @@ declare global {
971980
'external-import-b': HTMLExternalImportBElement;
972981
'external-import-c': HTMLExternalImportCElement;
973982
'factory-jsx': HTMLFactoryJsxElement;
983+
'image-import': HTMLImageImportElement;
974984
'init-css-root': HTMLInitCssRootElement;
975985
'input-basic-root': HTMLInputBasicRootElement;
976986
'key-reorder': HTMLKeyReorderElement;
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { Component, h } from '@stencil/core';
2+
import stencilLogo from './stencil-logo.svg';
3+
4+
@Component({
5+
tag: 'image-import'
6+
})
7+
export class ImageImport {
8+
9+
render() {
10+
return (
11+
<div>
12+
<img src={stencilLogo} />
13+
</div>
14+
);
15+
}
16+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<!DOCTYPE html>
2+
<meta charset="utf8">
3+
<script src="/build/testapp.esm.js" type="module"></script>
4+
<script src="/build/testapp.js" nomodule></script>
5+
6+
<image-import></image-import>

0 commit comments

Comments
 (0)