1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 | import MiniCssExtractPlugin from 'mini-css-extract-plugin';
|
7 | import miniSVGDataURI from 'mini-svg-data-uri';
|
8 |
|
9 | import * as webpack from 'webpack';
|
10 | import * as fs from 'fs-extra';
|
11 | import * as glob from 'glob';
|
12 | import * as path from 'path';
|
13 |
|
14 |
|
15 |
|
16 |
|
17 | export namespace Build {
|
18 | |
19 |
|
20 |
|
21 |
|
22 | export interface IEnsureOptions {
|
23 | |
24 |
|
25 |
|
26 | output: string;
|
27 |
|
28 | |
29 |
|
30 |
|
31 | schemaOutput?: string;
|
32 |
|
33 | |
34 |
|
35 |
|
36 | themeOutput?: string;
|
37 |
|
38 | |
39 |
|
40 |
|
41 | packageNames: ReadonlyArray<string>;
|
42 |
|
43 | |
44 |
|
45 |
|
46 | packagePaths?: ReadonlyArray<string>;
|
47 | }
|
48 |
|
49 | |
50 |
|
51 |
|
52 | export interface ILabExtension {
|
53 | |
54 |
|
55 |
|
56 |
|
57 |
|
58 |
|
59 |
|
60 |
|
61 |
|
62 |
|
63 | readonly extension?: boolean | string;
|
64 |
|
65 | |
66 |
|
67 |
|
68 |
|
69 |
|
70 |
|
71 |
|
72 |
|
73 |
|
74 |
|
75 | readonly mimeExtension?: boolean | string;
|
76 |
|
77 | |
78 |
|
79 |
|
80 | readonly schemaDir?: string;
|
81 |
|
82 | |
83 |
|
84 |
|
85 | readonly themePath?: string;
|
86 | }
|
87 |
|
88 | |
89 |
|
90 |
|
91 | export interface IModule {
|
92 | |
93 |
|
94 |
|
95 | jupyterlab?: ILabExtension;
|
96 |
|
97 | |
98 |
|
99 |
|
100 | main?: string;
|
101 |
|
102 | |
103 |
|
104 |
|
105 | name: string;
|
106 | }
|
107 |
|
108 | |
109 |
|
110 |
|
111 |
|
112 |
|
113 | export function ensureAssets(
|
114 | options: IEnsureOptions
|
115 | ): webpack.Configuration[] {
|
116 | const {
|
117 | output,
|
118 | schemaOutput = output,
|
119 | themeOutput = output,
|
120 | packageNames
|
121 | } = options;
|
122 |
|
123 | const themeConfig: webpack.Configuration[] = [];
|
124 |
|
125 | const packagePaths: string[] = options.packagePaths?.slice() || [];
|
126 |
|
127 | let cssImports: string[] = [];
|
128 |
|
129 | packageNames.forEach(name => {
|
130 | packagePaths.push(
|
131 | path.dirname(require.resolve(path.join(name, 'package.json')))
|
132 | );
|
133 | });
|
134 |
|
135 | packagePaths.forEach(packagePath => {
|
136 | const packageDataPath = require.resolve(
|
137 | path.join(packagePath, 'package.json')
|
138 | );
|
139 | const packageDir = path.dirname(packageDataPath);
|
140 | const data = fs.readJSONSync(packageDataPath);
|
141 | const name = data.name;
|
142 | const extension = normalizeExtension(data);
|
143 |
|
144 | const { schemaDir, themePath } = extension;
|
145 |
|
146 |
|
147 |
|
148 | if (typeof data.styleModule === 'string') {
|
149 | cssImports.push(`${name}/${data.styleModule}`);
|
150 | } else if (typeof data.style === 'string') {
|
151 | cssImports.push(`${name}/${data.style}`);
|
152 | }
|
153 |
|
154 |
|
155 | if (schemaDir) {
|
156 | const schemas = glob.sync(
|
157 | path.join(path.join(packageDir, schemaDir), '*')
|
158 | );
|
159 | const destination = path.join(schemaOutput, 'schemas', name);
|
160 |
|
161 |
|
162 | if (fs.existsSync(destination)) {
|
163 | try {
|
164 | const oldPackagePath = path.join(destination, 'package.json.orig');
|
165 | const oldPackageData = fs.readJSONSync(oldPackagePath);
|
166 | if (oldPackageData.version === data.version) {
|
167 | fs.removeSync(destination);
|
168 | }
|
169 | } catch (e) {
|
170 | fs.removeSync(destination);
|
171 | }
|
172 | }
|
173 |
|
174 |
|
175 | fs.mkdirpSync(destination);
|
176 |
|
177 |
|
178 | schemas.forEach(schema => {
|
179 | const file = path.basename(schema);
|
180 | fs.copySync(schema, path.join(destination, file));
|
181 | });
|
182 |
|
183 |
|
184 | fs.copySync(
|
185 | path.join(packageDir, 'package.json'),
|
186 | path.join(destination, 'package.json.orig')
|
187 | );
|
188 | }
|
189 |
|
190 | if (!themePath) {
|
191 | return;
|
192 | }
|
193 | themeConfig.push({
|
194 | mode: 'production',
|
195 | entry: {
|
196 | index: path.join(packageDir, themePath)
|
197 | },
|
198 | output: {
|
199 | path: path.resolve(path.join(themeOutput, 'themes', name)),
|
200 |
|
201 | filename: '[name].js',
|
202 | hashFunction: 'sha256'
|
203 | },
|
204 | module: {
|
205 | rules: [
|
206 | {
|
207 | test: /\.css$/,
|
208 | use: [MiniCssExtractPlugin.loader, 'css-loader']
|
209 | },
|
210 | {
|
211 | test: /\.svg/,
|
212 | type: 'asset/inline',
|
213 | generator: {
|
214 | dataUrl: (content: any) => miniSVGDataURI(content.toString())
|
215 | }
|
216 | },
|
217 | {
|
218 | test: /\.(cur|png|jpg|gif|ttf|woff|woff2|eot)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
|
219 | type: 'asset'
|
220 | }
|
221 | ]
|
222 | },
|
223 | plugins: [
|
224 | new MiniCssExtractPlugin({
|
225 |
|
226 |
|
227 | filename: '[name].css',
|
228 | chunkFilename: '[id].css'
|
229 | })
|
230 | ]
|
231 | });
|
232 | });
|
233 |
|
234 | cssImports.sort((a, b) => a.localeCompare(b));
|
235 | const styleContents = `/* This is a generated file of CSS imports */
|
236 | /* It was generated by @jupyterlab/builder in Build.ensureAssets() */
|
237 |
|
238 | ${cssImports.map(x => `import '${x}';`).join('\n')}
|
239 | `;
|
240 |
|
241 | const stylePath = path.join(output, 'style.js');
|
242 |
|
243 | // Make sure the output dir exists before writing to it.
|
244 | if (!fs.existsSync(output)) {
|
245 | fs.mkdirSync(output);
|
246 | }
|
247 | fs.writeFileSync(stylePath, styleContents, {
|
248 | encoding: 'utf8'
|
249 | });
|
250 |
|
251 | return themeConfig;
|
252 | }
|
253 |
|
254 | /**
|
255 | * Returns JupyterLab extension metadata from a module.
|
256 | */
|
257 | export function normalizeExtension(module: IModule): ILabExtension {
|
258 | let { jupyterlab, main, name } = module;
|
259 |
|
260 | main = main || 'index.js';
|
261 |
|
262 | if (!jupyterlab) {
|
263 | throw new Error(`Module ${name} does not contain JupyterLab metadata.`);
|
264 | }
|
265 |
|
266 | let { extension, mimeExtension, schemaDir, themePath } = jupyterlab;
|
267 |
|
268 | extension = extension === true ? main : extension;
|
269 | mimeExtension = mimeExtension === true ? main : mimeExtension;
|
270 |
|
271 | if (extension && mimeExtension && extension === mimeExtension) {
|
272 | const message = 'extension and mimeExtension cannot be the same export.';
|
273 |
|
274 | throw new Error(message);
|
275 | }
|
276 |
|
277 | return { extension, mimeExtension, schemaDir, themePath };
|
278 | }
|
279 | }
|
280 |
|
\ | No newline at end of file |