Skip to content
This repository was archived by the owner on Jan 11, 2023. It is now read-only.

Commit 1b9b559

Browse files
committed
include css depended upon by entry point, even if also depended on by a lazily-loaded component
1 parent abcac75 commit 1b9b559

File tree

1 file changed

+125
-119
lines changed

1 file changed

+125
-119
lines changed

src/core/create_compilers/extract_css.ts

Lines changed: 125 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import * as path from 'path';
33
import hash from 'string-hash';
44
import * as codec from 'sourcemap-codec';
55
import { PageComponent, Dirs } from '../../interfaces';
6-
import { CompileResult } from './interfaces';
6+
import { CompileResult, Chunk } from './interfaces';
77
import { posixify } from '../../utils'
88

99
const inline_sourcemap_header = 'data:application/json;charset=utf-8;base64,';
@@ -46,6 +46,65 @@ type SourceMap = {
4646
mappings: string;
4747
};
4848

49+
function get_css_from_modules(modules: string[], css_map: Map<string, string>, dirs: Dirs) {
50+
const parts: string[] = [];
51+
const mappings: number[][][] = [];
52+
53+
const combined_map: SourceMap = {
54+
version: 3,
55+
file: null,
56+
sources: [],
57+
sourcesContent: [],
58+
names: [],
59+
mappings: null
60+
};
61+
62+
modules.forEach(module => {
63+
if (!/\.css$/.test(module)) return;
64+
65+
const css = css_map.get(module);
66+
67+
const { code, map } = extract_sourcemap(css, module);
68+
69+
parts.push(code);
70+
71+
if (map) {
72+
const lines = codec.decode(map.mappings);
73+
74+
if (combined_map.sources.length > 0 || combined_map.names.length > 0) {
75+
lines.forEach(line => {
76+
line.forEach(segment => {
77+
// adjust source index
78+
segment[1] += combined_map.sources.length;
79+
80+
// adjust name index
81+
if (segment[4]) segment[4] += combined_map.names.length;
82+
});
83+
});
84+
}
85+
86+
combined_map.sources.push(...map.sources);
87+
combined_map.sourcesContent.push(...map.sourcesContent);
88+
combined_map.names.push(...map.names);
89+
90+
mappings.push(...lines);
91+
}
92+
});
93+
94+
if (parts.length > 0) {
95+
combined_map.mappings = codec.encode(mappings);
96+
97+
combined_map.sources = combined_map.sources.map(source => path.relative(`${dirs.dest}/client`, source));
98+
99+
return {
100+
code: parts.join('\n'),
101+
map: combined_map
102+
};
103+
}
104+
105+
return null;
106+
}
107+
49108
export default function extract_css(client_result: CompileResult, components: PageComponent[], dirs: Dirs) {
50109
const result: {
51110
main: string | null;
@@ -57,151 +116,94 @@ export default function extract_css(client_result: CompileResult, components: Pa
57116

58117
if (!client_result.css_files) return; // Rollup-only for now
59118

60-
const unaccounted_for = new Set();
119+
let asset_dir = `${dirs.dest}/client`;
120+
if (process.env.SAPPER_LEGACY_BUILD) asset_dir += '/legacy';
61121

62-
const css_map = new Map();
63-
client_result.css_files.forEach(css => {
64-
unaccounted_for.add(css.id);
65-
css_map.set(css.id, css.code);
66-
});
122+
const unclaimed = new Set(client_result.css_files.map(x => x.id));
67123

68-
const chunk_map = new Map();
124+
const lookup = new Map();
69125
client_result.chunks.forEach(chunk => {
70-
chunk_map.set(chunk.file, chunk);
126+
lookup.set(chunk.file, chunk);
71127
});
72128

73-
const chunks_with_css = new Set();
74-
75-
// figure out which chunks belong to which components...
76-
const component_owners = new Map();
77-
client_result.chunks.forEach(chunk => {
78-
chunk.modules.forEach(module => {
79-
const component = posixify(path.relative(dirs.routes, module));
80-
component_owners.set(component, chunk);
81-
});
129+
const css_map = new Map();
130+
client_result.css_files.forEach(css_module => {
131+
css_map.set(css_module.id, css_module.code);
82132
});
83133

84-
const chunks_depended_upon_by_component = new Map();
85-
86-
// ...so we can figure out which chunks don't belong
87-
components.forEach(component => {
88-
const chunk = component_owners.get(component.file);
89-
if (!chunk) {
90-
// this should never happen!
91-
throw new Error(`Could not find chunk that owns ${component.file}`);
92-
}
93-
94-
const chunks = new Set([chunk]);
95-
chunks.forEach(chunk => {
96-
chunk.imports.forEach((file: string) => {
97-
const chunk = chunk_map.get(file);
98-
if (chunk) chunks.add(chunk);
99-
});
100-
});
101-
102-
chunks.forEach(chunk => {
103-
chunk.modules.forEach((module: string) => {
104-
unaccounted_for.delete(module);
105-
});
106-
});
107-
108-
chunks_depended_upon_by_component.set(
109-
component,
110-
chunks
111-
);
112-
});
134+
const chunks_with_css = new Set();
113135

114-
function get_css_from_modules(modules: string[]) {
115-
const parts: string[] = [];
116-
const mappings: number[][][] = [];
117-
118-
const combined_map: SourceMap = {
119-
version: 3,
120-
file: null,
121-
sources: [],
122-
sourcesContent: [],
123-
names: [],
124-
mappings: null
125-
};
136+
// concatenate and emit CSS
137+
client_result.chunks.forEach(chunk => {
138+
const css_modules = chunk.modules.filter(m => css_map.has(m));
139+
if (!css_modules.length) return;
126140

127-
modules.forEach(module => {
128-
if (!/\.css$/.test(module)) return;
141+
const css = get_css_from_modules(css_modules, css_map, dirs);
129142

130-
const css = css_map.get(module);
143+
const { code, map } = css;
131144

132-
const { code, map } = extract_sourcemap(css, module);
145+
const output_file_name = chunk.file.replace(/\.js$/, '.css');
133146

134-
parts.push(code);
147+
map.file = output_file_name;
148+
map.sources = map.sources.map(source => path.relative(`${asset_dir}`, source));
135149

136-
if (map) {
137-
const lines = codec.decode(map.mappings);
150+
fs.writeFileSync(`${asset_dir}/${output_file_name}`, `${code}\n/* sourceMappingURL=./${output_file_name}.map */`);
151+
fs.writeFileSync(`${asset_dir}/${output_file_name}.map`, JSON.stringify(map, null, ' '));
138152

139-
if (combined_map.sources.length > 0 || combined_map.names.length > 0) {
140-
lines.forEach(line => {
141-
line.forEach(segment => {
142-
// adjust source index
143-
segment[1] += combined_map.sources.length;
153+
chunks_with_css.add(chunk);
154+
});
144155

145-
// adjust name index
146-
if (segment[4]) segment[4] += combined_map.names.length;
147-
});
148-
});
149-
}
156+
const entry = path.resolve(dirs.src, 'client.js');
157+
const entry_chunk = client_result.chunks.find(chunk => chunk.modules.indexOf(entry) !== -1);
150158

151-
combined_map.sources.push(...map.sources);
152-
combined_map.sourcesContent.push(...map.sourcesContent);
153-
combined_map.names.push(...map.names);
159+
const entry_chunk_dependencies: Set<Chunk> = new Set([entry_chunk]);
160+
const entry_css_modules: string[] = [];
154161

155-
mappings.push(...lines);
156-
}
162+
// recursively find the chunks this component depends on
163+
entry_chunk_dependencies.forEach(chunk => {
164+
chunk.imports.forEach(file => {
165+
entry_chunk_dependencies.add(lookup.get(file));
157166
});
158167

159-
if (parts.length > 0) {
160-
combined_map.mappings = codec.encode(mappings);
161-
162-
combined_map.sources = combined_map.sources.map(source => path.relative(`${dirs.dest}/client`, source));
163-
164-
return {
165-
code: parts.join('\n'),
166-
map: combined_map
167-
};
168+
if (chunks_with_css.has(chunk)) {
169+
chunk.modules.forEach(file => {
170+
unclaimed.delete(file);
171+
if (css_map.has(file)) {
172+
entry_css_modules.push(file);
173+
}
174+
});
168175
}
176+
});
169177

170-
return null;
171-
}
172-
173-
let asset_dir = `${dirs.dest}/client`;
174-
if (process.env.SAPPER_LEGACY_BUILD) asset_dir += '/legacy';
175-
176-
const replacements = new Map();
177-
178-
chunks_depended_upon_by_component.forEach((chunks, component) => {
179-
const chunks_with_css = Array.from(chunks).filter(chunk => {
180-
const css = get_css_from_modules(chunk.modules);
178+
// figure out which (css-having) chunks each component depends on
179+
components.forEach(component => {
180+
const resolved = path.resolve(dirs.routes, component.file);
181+
const chunk: Chunk = client_result.chunks.find(chunk => chunk.modules.indexOf(resolved) !== -1);
181182

182-
if (css) {
183-
const { code, map } = css;
183+
if (!chunk) {
184+
// this should never happen!
185+
throw new Error(`Could not find chunk that owns ${component.file}`);
186+
}
184187

185-
const output_file_name = chunk.file.replace(/\.js$/, '.css');
188+
const chunk_dependencies: Set<Chunk> = new Set([chunk]);
189+
const css_dependencies: string[] = [];
186190

187-
map.file = output_file_name;
188-
map.sources = map.sources.map(source => path.relative(`${asset_dir}`, source));
191+
// recursively find the chunks this component depends on
192+
chunk_dependencies.forEach(chunk => {
193+
chunk.imports.forEach(file => {
194+
chunk_dependencies.add(lookup.get(file));
195+
});
189196

190-
fs.writeFileSync(`${asset_dir}/${output_file_name}`, `${code}\n/* sourceMappingURL=./${output_file_name}.map */`);
191-
fs.writeFileSync(`${asset_dir}/${output_file_name}.map`, JSON.stringify(map, null, ' '));
197+
if (chunks_with_css.has(chunk)) {
198+
css_dependencies.push(chunk.file);
192199

193-
return true;
200+
chunk.modules.forEach(file => {
201+
unclaimed.delete(file);
202+
});
194203
}
195204
});
196205

197-
const files = chunks_with_css.map(chunk => chunk.file.replace(/\.js$/, '.css'));
198-
199-
replacements.set(
200-
component.file,
201-
files
202-
);
203-
204-
result.chunks[component.file] = files;
206+
result.chunks[component.file] = css_dependencies;
205207
});
206208

207209
fs.readdirSync(asset_dir).forEach(file => {
@@ -210,13 +212,17 @@ export default function extract_css(client_result: CompileResult, components: Pa
210212
const source = fs.readFileSync(`${asset_dir}/${file}`, 'utf-8');
211213

212214
const replaced = source.replace(/["']__SAPPER_CSS_PLACEHOLDER:(.+?)__["']/g, (m, route) => {
213-
return JSON.stringify(replacements.get(route));
215+
return JSON.stringify(result.chunks[route]);
214216
});
215217

216218
fs.writeFileSync(`${asset_dir}/${file}`, replaced);
217219
});
218220

219-
const leftover = get_css_from_modules(Array.from(unaccounted_for));
221+
unclaimed.forEach(file => {
222+
entry_css_modules.push(css_map.get(file));
223+
});
224+
225+
const leftover = get_css_from_modules(entry_css_modules, css_map, dirs);
220226
if (leftover) {
221227
const { code, map } = leftover;
222228

0 commit comments

Comments
 (0)