Skip to content

Commit 71acf87

Browse files
committed
Update svgr and use project svgo version
1 parent 584b9b3 commit 71acf87

4 files changed

Lines changed: 329 additions & 142 deletions

File tree

packages/core/integration-tests/test/svg-react.js

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import assert from 'assert';
2-
import {bundle, outputFS} from '@parcel/test-utils';
2+
import {bundle, outputFS, fsFixture, overlayFS} from '@parcel/test-utils';
33
import path from 'path';
4+
import Logger from '@parcel/logger';
5+
import {md} from '@parcel/diagnostic';
46

57
describe('svg-react', function () {
68
it('should support transforming SVGs to react components', async function () {
@@ -56,4 +58,81 @@ describe('svg-react', function () {
5658
assert(file.includes('(0, _preact.h)("svg"'));
5759
assert(file.includes('width: "1em"'));
5860
});
61+
62+
it('should detect the version of SVGO to use', async function () {
63+
// Test is outside parcel so that svgo is not already installed.
64+
await fsFixture(overlayFS, '/')`
65+
svgr-svgo-version
66+
icon.svg:
67+
<svg></svg>
68+
69+
index.html:
70+
<img src="icon.svg" />
71+
72+
svgo.config.json:
73+
{
74+
"full": true
75+
}
76+
77+
yarn.lock:
78+
`;
79+
80+
let messages = [];
81+
let loggerDisposable = Logger.onLog(message => {
82+
if (message.level !== 'verbose') {
83+
messages.push(message);
84+
}
85+
});
86+
87+
try {
88+
await bundle(path.join('/svgr-svgo-version/index.html'), {
89+
inputFS: overlayFS,
90+
defaultTargetOptions: {
91+
shouldOptimize: true,
92+
},
93+
shouldAutoinstall: false,
94+
defaultConfig: path.join(
95+
__dirname,
96+
'integration/custom-configs/.parcelrc-svg-react',
97+
),
98+
});
99+
} catch (err) {
100+
// autoinstall is disabled
101+
assert.equal(
102+
err.diagnostics[0].message,
103+
md`Could not resolve module "svgo" from "${path.resolve(
104+
overlayFS.cwd(),
105+
'/svgr-svgo-version/index',
106+
)}"`,
107+
);
108+
}
109+
110+
loggerDisposable.dispose();
111+
assert(
112+
messages[0].diagnostics[0].message.startsWith(
113+
'Detected deprecated SVGO v2 options in',
114+
),
115+
);
116+
assert.deepEqual(messages[0].diagnostics[0].codeFrames, [
117+
{
118+
filePath: path.resolve(
119+
overlayFS.cwd(),
120+
'/svgr-svgo-version/svgo.config.json',
121+
),
122+
codeHighlights: [
123+
{
124+
message: undefined,
125+
start: {
126+
line: 2,
127+
column: 3,
128+
},
129+
end: {
130+
line: 2,
131+
column: 14,
132+
},
133+
},
134+
],
135+
},
136+
]);
137+
});
59138
});

packages/transformers/svg-react/package.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,10 @@
2020
"parcel": "^2.12.0"
2121
},
2222
"dependencies": {
23+
"@parcel/diagnostic": "2.12.0",
2324
"@parcel/plugin": "2.12.0",
24-
"@svgr/core": "^6.2.0",
25-
"@svgr/plugin-jsx": "^6.2.0",
26-
"@svgr/plugin-svgo": "^6.2.0"
25+
"@parcel/utils": "2.12.0",
26+
"@svgr/core": "^8.0.0",
27+
"@svgr/plugin-jsx": "^8.0.0"
2728
}
2829
}

packages/transformers/svg-react/src/SvgReactTransformer.js

Lines changed: 130 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@
22

33
import {Transformer} from '@parcel/plugin';
44

5-
import svgoPlugin from '@svgr/plugin-svgo';
65
import jsxPlugin from '@svgr/plugin-jsx';
76
import {transform} from '@svgr/core';
7+
import {detectSVGOVersion} from '@parcel/utils';
8+
import path from 'path';
9+
import {md, generateJSONCodeHighlights} from '@parcel/diagnostic';
810

911
export default (new Transformer({
10-
async loadConfig({config}) {
12+
async loadConfig({config, logger, options}) {
1113
let svgrResult = await config.getConfig([
1214
'.svgrrc',
1315
'.svgrrc.json',
@@ -19,25 +21,102 @@ export default (new Transformer({
1921
'svgr.config.cjs',
2022
'svgr.config.mjs',
2123
]);
22-
let svgoResult = await config.getConfig([
24+
let svgoResult: any = await config.getConfig([
2325
'svgo.config.js',
2426
'svgo.config.cjs',
2527
'svgo.config.mjs',
2628
'svgo.config.json',
2729
]);
28-
return {svgr: svgrResult?.contents, svgo: svgoResult?.contents};
30+
31+
let svgoConfig = svgrResult?.contents?.svgoConfig ?? svgoResult?.contents;
32+
let svgoConfigPath = svgrResult?.contents?.svgoConfig
33+
? svgrResult.filePath
34+
: svgoResult?.filePath;
35+
36+
// See if svgo is already installed.
37+
let resolved;
38+
try {
39+
resolved = await options.packageManager.resolve(
40+
'svgo',
41+
path.join(options.projectRoot, 'index'),
42+
{shouldAutoInstall: false},
43+
);
44+
} catch (err) {
45+
// ignore.
46+
}
47+
48+
// If so, use the existing installed version.
49+
let svgoVersion = 3;
50+
if (resolved) {
51+
if (resolved.pkg?.version) {
52+
svgoVersion = parseInt(resolved.pkg.version);
53+
}
54+
} else if (svgoConfig) {
55+
// Otherwise try to detect the version based on the config file.
56+
let v = detectSVGOVersion(svgoConfig);
57+
if (svgoConfig != null && v.version === 2) {
58+
logger.warn({
59+
message: md`Detected deprecated SVGO v2 options in ${path.relative(
60+
process.cwd(),
61+
svgoConfigPath,
62+
)}`,
63+
codeFrames: [
64+
{
65+
filePath: svgoConfigPath,
66+
codeHighlights:
67+
path.basename(svgoConfigPath) === '.svgrrc' ||
68+
path.extname(svgoConfigPath) === '.json'
69+
? generateJSONCodeHighlights(
70+
await options.inputFS.readFile(svgoConfigPath, 'utf8'),
71+
[
72+
{
73+
key: `${
74+
svgrResult?.contents?.svgoConfig
75+
? '/svgoConfig'
76+
: ''
77+
}${v.path}`,
78+
},
79+
],
80+
)
81+
: [],
82+
},
83+
],
84+
});
85+
}
86+
87+
svgoVersion = v.version;
88+
}
89+
90+
return {svgr: svgrResult?.contents, svgo: svgoConfig, svgoVersion};
2991
},
3092

31-
async transform({asset, config}) {
93+
async transform({asset, config, options}) {
3294
let code = await asset.getCode();
3395

96+
let plugins = [];
97+
if (config.svgr?.svgo !== false) {
98+
let svgo = await options.packageManager.require(
99+
'svgo',
100+
path.join(options.projectRoot, 'index'),
101+
{
102+
range: `^${config.svgoVersion}`,
103+
saveDev: true,
104+
shouldAutoInstall: options.shouldAutoInstall,
105+
},
106+
);
107+
108+
plugins.push(createSvgoPlugin(svgo));
109+
}
110+
111+
plugins.push(jsxPlugin);
112+
34113
const jsx = await transform(
35114
code,
36115
{svgoConfig: config.svgo, ...config.svgr, runtimeConfig: false},
37116
{
38117
caller: {
39118
name: '@parcel/transformer-svg-react',
40-
defaultPlugins: [svgoPlugin, jsxPlugin],
119+
defaultPlugins: plugins,
41120
},
42121
filePath: asset.filePath,
43122
},
@@ -50,3 +129,48 @@ export default (new Transformer({
50129
return [asset];
51130
},
52131
}): Transformer);
132+
133+
// Below is copied from @svgr/plugin-svgo. MIT license.
134+
// https://github.com/gregberge/svgr/tree/180eb6d503215fc782dfece351ff751194a0dfed/packages/plugin-svgo
135+
136+
function getSvgoConfigFromSvgrConfig(config) {
137+
const params = {overrides: {}};
138+
if (config.icon || config.dimensions === false) {
139+
params.overrides.removeViewBox = false;
140+
}
141+
if (config.native) {
142+
params.overrides.inlineStyles = {
143+
onlyMatchedOnce: false,
144+
};
145+
}
146+
147+
return {
148+
plugins: [
149+
{
150+
name: 'preset-default',
151+
params,
152+
},
153+
'prefixIds',
154+
],
155+
};
156+
}
157+
158+
function getSvgoConfig(config) {
159+
if (config.svgoConfig) return config.svgoConfig;
160+
return getSvgoConfigFromSvgrConfig(config);
161+
}
162+
163+
function createSvgoPlugin(svgo) {
164+
return (code, config, state) => {
165+
const svgoConfig = getSvgoConfig(config);
166+
const result = svgo.optimize(code, {...svgoConfig, path: state.filePath});
167+
168+
// @ts-ignore
169+
if (result.modernError) {
170+
// @ts-ignore
171+
throw result.modernError;
172+
}
173+
174+
return result.data;
175+
};
176+
}

0 commit comments

Comments
 (0)