UNPKG

4.76 kBJavaScriptView Raw
1/* eslint no-unused-expressions: 0 */
2const Metalsmith = require('metalsmith');
3const uppercamelcase = require('uppercamelcase');
4const async = require('async');
5const ejsRender = require('consolidate').ejs.render;
6const multimatch = require('multimatch');
7const kebabCase = require('kebab-case');
8const { getNpmRegistry } = require('ice-npm-utils');
9const transform = require('./transform');
10const logger = require('./logger');
11
12const TEMPLATE_PATH = '.template';
13
14module.exports = (options) => {
15 return new Promise((resolve, reject) => {
16 render(options, (err) => {
17 if (err) {
18 reject(err);
19 } else {
20 resolve();
21 }
22 });
23 });
24};
25
26/**
27 * Generate a template given a `src` and `dest`.
28 *
29 * @param {String} name
30 * @param {String} npmName
31 * @param {String} src
32 * @param {String} dest
33 * @param {Function} done
34 */
35function render(options, done) {
36 const {
37 src,
38 dest,
39 name = '',
40 npmName,
41 transformRegexp,
42 ...opts
43 } = options;
44
45 logger.verbose('Metalsmith template render', { name, src, dest });
46
47 // const metalsmith = Metalsmith(path.join(src, 'template'));
48 const metalsmith = Metalsmith(src);
49 metalsmith.frontmatter(false);
50
51 const data = Object.assign(metalsmith.metadata(), {
52 name: kebabCase(name).replace(/^-/, ''),
53 npmName: kebabCase(npmName || name).replace(/^-/, ''),
54 className: uppercamelcase(name),
55 inPlace: dest === process.cwd(),
56 noEscape: true,
57 registry: getNpmRegistry(npmName || name),
58 categories: {}, // TODO: 已废弃,使用 category,下版本移除
59 category: '',
60 ...opts,
61 });
62 logger.verbose('Metalsmith render options', data);
63
64 metalsmith
65 .use(renderTemplateFiles())
66 .use(transformFile(data.skipGitIgnore, transformRegexp))
67 .ignore([TEMPLATE_PATH, 'meta.js']);
68
69 metalsmith
70 .clean(false)
71 .source('.') // start from template root instead of `./src` which is Metalsmith's default for `source`
72 .destination(dest)
73 .build((err) => {
74 // 需要显性控制从物料 meta.js 中提取出来的 message 展现时机
75 done(err, () => {
76 logMessage(data);
77 });
78 });
79
80 return data;
81}
82
83function transformFile(skipGitIgnore, transformRegexp) {
84 return (files, metalsmith, done) => {
85 transform(files, {
86 ...metalsmith.metadata(),
87 skipGitIgnore,
88 transformRegexp,
89 }, done);
90 };
91}
92
93/**
94 * Template in place plugin.
95 *
96 * @param {Object} files
97 * @param {Metalsmith} metalsmith
98 * @param {Function} done
99 */
100function renderTemplateFiles(skipInterpolation) {
101 skipInterpolation =
102 typeof skipInterpolation === 'string'
103 ? [skipInterpolation]
104 : skipInterpolation;
105
106 logger.verbose('Metasmith renderTemplateFiles', skipInterpolation);
107
108 return (files, metalsmith, done) => {
109 logger.verbose('Metasmith renderTemplateFiles callback');
110
111 const keys = Object.keys(files);
112 const metalsmithMetadata = metalsmith.metadata();
113
114 // HACK: need refactor
115 Object.keys(metalsmithMetadata).forEach((key) => {
116 if (key === 'type') {
117 if (
118 metalsmithMetadata[key].react &&
119 metalsmithMetadata[key].vue
120 ) {
121 metalsmithMetadata.materialType = ['react', 'vue'];
122 } else if (metalsmithMetadata[key].react) {
123 metalsmithMetadata.materialType = 'react';
124 } else if (metalsmithMetadata[key].vue) {
125 metalsmithMetadata.materialType = 'vue';
126 }
127 }
128 });
129
130 async.each(
131 keys,
132 (file, next) => {
133 // skipping files with skipInterpolation option
134 if (
135 skipInterpolation &&
136 multimatch([file], skipInterpolation, { dot: true }).length
137 ) {
138 return next();
139 }
140 const str = files[file].contents.toString();
141
142 logger.verbose('ejsRender start', file);
143 ejsRender(str, metalsmithMetadata, (err, res) => {
144 if (err) {
145 err.message = `[${file}] ${err.message}`;
146 return next(err);
147 }
148
149 logger.verbose('ejsRender success', file);
150 /* eslint-disable-next-line no-buffer-constructor */
151 files[file].contents = new Buffer(res);
152 next();
153 });
154 },
155 done
156 );
157 };
158}
159
160/**
161 * Display template complete message.
162 *
163 * @param {String} message
164 * @param {Object} data
165 */
166function logMessage(message, data) {
167 if (!message) return;
168 ejsRender(message, data, (err, res) => {
169 if (err) {
170 console.error(
171 `\n Error when rendering template complete message: ${
172 err.message.trim()}`
173 );
174 } else {
175 console.log(
176 `\n${
177 res
178 .split(/\r?\n/g)
179 .map((line) => ` ${line}`)
180 .join('\n')}`
181 );
182 }
183 });
184}