Webpack 不解析来自第三方库的扩展
Webpack is not resolving extensions from third party libraries
我正在开发 2 Angular 2 个库:
- carbonldp-workbench
- carbonldp-panel
项目 2(面板):
是正在导出的组件和模块的 collection。这些组件有自己的模板和 scss 文件。该库是用 Typescript 编写的,但正在编译 dist
文件,因此该库作为 npm
包提供,带有自己的 .js
文件及其 .metadata.json
和 .d.ts
个文件。
项目 1 (Workbench)
是主项目。它使用 Project2 所以它可以工作。
这个项目使用 Webpack 来捆绑最终的应用程序。问题是我不能让 webpack 与 Project2 (Panel) 一起工作,因为主项目是用 typescript 编写的,而 panel 只用 javascript 文件导入。
我从 webapack 得到的错误是无法找到 Project2 中的所有模板文件。见下文:
这就是最终 app.js 捆绑 Project2 库的方式,请注意来自 的 header 组件的方式Project2,正在导入模板文件:
并注意 webpack 如何从 main 项目 Project1(用 ts 编写)导入模板和样式文件:
这就是我在 webpack.config.js
文件中定义解析扩展规则的方式:
entry: {
"polyfills": "./src/polyfills.ts",
"app": "./src/main.ts"
},
resolve: {
extensions: [ ".ts", ".js" ],
alias: {
"app": helpers.root( "src", "app" ),
"jquery": "jquery/src/jquery",
"semantic-ui": helpers.root( "src/semantic/dist" ),
},
modules: [ helpers.root( "node_modules" ) ]
},
module: {
rules: [
{
test: /\.ts$/,
use: [ "awesome-typescript-loader", "angular2-template-loader", "angular-router-loader" ]
},
{
test: /\.html$/,
use: "raw-loader",
exclude: [ helpers.root( "src/index.html" ) ]
},
{
test: /\.(png|jpe?g|gif|svg|woff|woff2|ttf|eot|ico)$/,
use: "file-loader?name=assets/[name].[hash].[ext]"
},
{
test: /\.s?css$/,
use: [ "raw-loader", "sass-loader" ]
},
]
},
这里的问题是... 我如何 让 Webpack 也编译我的 angular js 的导入图书馆?
根据我的经验,angular2-template-loader 不会深入研究 node_modules,因此 node_modules 中可能依赖于它的库将会倒霉。
我绕过它的方法是将外部模板和 css 文件作为库构建步骤的一部分内联,我在很大程度上基于 angular material 2 的发布方式他们的图书馆。然后 webpack - 在消费应用级别 - 不需要担心你的模板或样式表,因为它们已经准备好了。
为了不使用 link,这里是 他们的 脚本,我不相信它:
#!/usr/bin/env node
'use strict';
const fs = require('fs');
const path = require('path');
const glob = require('glob');
/**
* Simple Promiseify function that takes a Node API and return a version that supports promises.
* We use promises instead of synchronized functions to make the process less I/O bound and
* faster. It also simplify the code.
*/
function promiseify(fn) {
return function() {
const args = [].slice.call(arguments, 0);
return new Promise((resolve, reject) => {
fn.apply(this, args.concat([function (err, value) {
if (err) {
reject(err);
} else {
resolve(value);
}
}]));
});
};
}
const readFile = promiseify(fs.readFile);
const writeFile = promiseify(fs.writeFile);
function inlineResources(globs) {
if (typeof globs == 'string') {
globs = [globs];
}
/**
* For every argument, inline the templates and styles under it and write the new file.
*/
return Promise.all(globs.map(pattern => {
if (pattern.indexOf('*') < 0) {
// Argument is a directory target, add glob patterns to include every files.
pattern = path.join(pattern, '**', '*');
}
const files = glob.sync(pattern, {})
.filter(name => /\.js$/.test(name)); // Matches only JavaScript files.
// Generate all files content with inlined templates.
return Promise.all(files.map(filePath => {
return readFile(filePath, 'utf-8')
.then(content => inlineResourcesFromString(content, url => {
return path.join(path.dirname(filePath), url);
}))
.then(content => writeFile(filePath, content))
.catch(err => {
console.error('An error occurred: ', err);
});
}));
}));
}
/**
* Inline resources from a string content.
* @param content {string} The source file's content.
* @param urlResolver {Function} A resolver that takes a URL and return a path.
* @returns {string} The content with resources inlined.
*/
function inlineResourcesFromString(content, urlResolver) {
// Curry through the inlining functions.
return [
inlineTemplate,
inlineStyle,
removeModuleId
].reduce((content, fn) => fn(content, urlResolver), content);
}
if (require.main === module) {
inlineResources(process.argv.slice(2));
}
/**
* Inline the templates for a source file. Simply search for instances of `templateUrl: ...` and
* replace with `template: ...` (with the content of the file included).
* @param content {string} The source file's content.
* @param urlResolver {Function} A resolver that takes a URL and return a path.
* @return {string} The content with all templates inlined.
*/
function inlineTemplate(content, urlResolver) {
return content.replace(/templateUrl:\s*'([^']+?\.html)'/g, function(m, templateUrl) {
const templateFile = urlResolver(templateUrl);
const templateContent = fs.readFileSync(templateFile, 'utf-8');
const shortenedTemplate = templateContent
.replace(/([\n\r]\s*)+/gm, ' ')
.replace(/"/g, '\"');
return `template: "${shortenedTemplate}"`;
});
}
/**
* Inline the styles for a source file. Simply search for instances of `styleUrls: [...]` and
* replace with `styles: [...]` (with the content of the file included).
* @param urlResolver {Function} A resolver that takes a URL and return a path.
* @param content {string} The source file's content.
* @return {string} The content with all styles inlined.
*/
function inlineStyle(content, urlResolver) {
return content.replace(/styleUrls:\s*(\[[\s\S]*?\])/gm, function(m, styleUrls) {
const urls = eval(styleUrls);
return 'styles: ['
+ urls.map(styleUrl => {
const styleFile = urlResolver(styleUrl);
const styleContent = fs.readFileSync(styleFile, 'utf-8');
const shortenedStyle = styleContent
.replace(/([\n\r]\s*)+/gm, ' ')
.replace(/"/g, '\"');
return `"${shortenedStyle}"`;
})
.join(',\n')
+ ']';
});
}
/**
* Remove every mention of `moduleId: module.id`.
* @param content {string} The source file's content.
* @returns {string} The content with all moduleId: mentions removed.
*/
function removeModuleId(content) {
return content.replace(/\s*moduleId:\s*module\.id\s*,?\s*/gm, '');
}
module.exports = inlineResources;
module.exports.inlineResourcesFromString = inlineResourcesFromString;
他们将其用作 gulp 任务的一部分,我已将我的脚本改写为简单的节点脚本。
我在构建中所做的是:
- 从我的 src 文件夹创建一个 dist 文件夹(因此 html 和 css 文件保持相对),然后从 dist 中:
- 如果需要,可以在此处进行任何类型的 postcss 处理(使用 postcss 最好的选择是使用替换标志来维护文件夹结构)
- 运行 内联器(上面的脚本)
- 删除 html 和 css 文件(不再需要它们)
- 使用 ngc 转译
在第一步中,我开始使用一个暂存文件夹,然后转译到一个 dist 文件夹,但是 ngc 并没有与 outDir 一起玩。
我花了一些时间才弄明白这一点,希望我能为您节省一些时间。
我正在开发 2 Angular 2 个库:
- carbonldp-workbench
- carbonldp-panel
项目 2(面板):
是正在导出的组件和模块的 collection。这些组件有自己的模板和 scss 文件。该库是用 Typescript 编写的,但正在编译 dist
文件,因此该库作为 npm
包提供,带有自己的 .js
文件及其 .metadata.json
和 .d.ts
个文件。
项目 1 (Workbench)
是主项目。它使用 Project2 所以它可以工作。 这个项目使用 Webpack 来捆绑最终的应用程序。问题是我不能让 webpack 与 Project2 (Panel) 一起工作,因为主项目是用 typescript 编写的,而 panel 只用 javascript 文件导入。 我从 webapack 得到的错误是无法找到 Project2 中的所有模板文件。见下文:
这就是最终 app.js 捆绑 Project2 库的方式,请注意来自 的 header 组件的方式Project2,正在导入模板文件:
并注意 webpack 如何从 main 项目 Project1(用 ts 编写)导入模板和样式文件:
这就是我在 webpack.config.js
文件中定义解析扩展规则的方式:
entry: {
"polyfills": "./src/polyfills.ts",
"app": "./src/main.ts"
},
resolve: {
extensions: [ ".ts", ".js" ],
alias: {
"app": helpers.root( "src", "app" ),
"jquery": "jquery/src/jquery",
"semantic-ui": helpers.root( "src/semantic/dist" ),
},
modules: [ helpers.root( "node_modules" ) ]
},
module: {
rules: [
{
test: /\.ts$/,
use: [ "awesome-typescript-loader", "angular2-template-loader", "angular-router-loader" ]
},
{
test: /\.html$/,
use: "raw-loader",
exclude: [ helpers.root( "src/index.html" ) ]
},
{
test: /\.(png|jpe?g|gif|svg|woff|woff2|ttf|eot|ico)$/,
use: "file-loader?name=assets/[name].[hash].[ext]"
},
{
test: /\.s?css$/,
use: [ "raw-loader", "sass-loader" ]
},
]
},
这里的问题是... 我如何 让 Webpack 也编译我的 angular js 的导入图书馆?
根据我的经验,angular2-template-loader 不会深入研究 node_modules,因此 node_modules 中可能依赖于它的库将会倒霉。
我绕过它的方法是将外部模板和 css 文件作为库构建步骤的一部分内联,我在很大程度上基于 angular material 2 的发布方式他们的图书馆。然后 webpack - 在消费应用级别 - 不需要担心你的模板或样式表,因为它们已经准备好了。
为了不使用 link,这里是 他们的 脚本,我不相信它:
#!/usr/bin/env node
'use strict';
const fs = require('fs');
const path = require('path');
const glob = require('glob');
/**
* Simple Promiseify function that takes a Node API and return a version that supports promises.
* We use promises instead of synchronized functions to make the process less I/O bound and
* faster. It also simplify the code.
*/
function promiseify(fn) {
return function() {
const args = [].slice.call(arguments, 0);
return new Promise((resolve, reject) => {
fn.apply(this, args.concat([function (err, value) {
if (err) {
reject(err);
} else {
resolve(value);
}
}]));
});
};
}
const readFile = promiseify(fs.readFile);
const writeFile = promiseify(fs.writeFile);
function inlineResources(globs) {
if (typeof globs == 'string') {
globs = [globs];
}
/**
* For every argument, inline the templates and styles under it and write the new file.
*/
return Promise.all(globs.map(pattern => {
if (pattern.indexOf('*') < 0) {
// Argument is a directory target, add glob patterns to include every files.
pattern = path.join(pattern, '**', '*');
}
const files = glob.sync(pattern, {})
.filter(name => /\.js$/.test(name)); // Matches only JavaScript files.
// Generate all files content with inlined templates.
return Promise.all(files.map(filePath => {
return readFile(filePath, 'utf-8')
.then(content => inlineResourcesFromString(content, url => {
return path.join(path.dirname(filePath), url);
}))
.then(content => writeFile(filePath, content))
.catch(err => {
console.error('An error occurred: ', err);
});
}));
}));
}
/**
* Inline resources from a string content.
* @param content {string} The source file's content.
* @param urlResolver {Function} A resolver that takes a URL and return a path.
* @returns {string} The content with resources inlined.
*/
function inlineResourcesFromString(content, urlResolver) {
// Curry through the inlining functions.
return [
inlineTemplate,
inlineStyle,
removeModuleId
].reduce((content, fn) => fn(content, urlResolver), content);
}
if (require.main === module) {
inlineResources(process.argv.slice(2));
}
/**
* Inline the templates for a source file. Simply search for instances of `templateUrl: ...` and
* replace with `template: ...` (with the content of the file included).
* @param content {string} The source file's content.
* @param urlResolver {Function} A resolver that takes a URL and return a path.
* @return {string} The content with all templates inlined.
*/
function inlineTemplate(content, urlResolver) {
return content.replace(/templateUrl:\s*'([^']+?\.html)'/g, function(m, templateUrl) {
const templateFile = urlResolver(templateUrl);
const templateContent = fs.readFileSync(templateFile, 'utf-8');
const shortenedTemplate = templateContent
.replace(/([\n\r]\s*)+/gm, ' ')
.replace(/"/g, '\"');
return `template: "${shortenedTemplate}"`;
});
}
/**
* Inline the styles for a source file. Simply search for instances of `styleUrls: [...]` and
* replace with `styles: [...]` (with the content of the file included).
* @param urlResolver {Function} A resolver that takes a URL and return a path.
* @param content {string} The source file's content.
* @return {string} The content with all styles inlined.
*/
function inlineStyle(content, urlResolver) {
return content.replace(/styleUrls:\s*(\[[\s\S]*?\])/gm, function(m, styleUrls) {
const urls = eval(styleUrls);
return 'styles: ['
+ urls.map(styleUrl => {
const styleFile = urlResolver(styleUrl);
const styleContent = fs.readFileSync(styleFile, 'utf-8');
const shortenedStyle = styleContent
.replace(/([\n\r]\s*)+/gm, ' ')
.replace(/"/g, '\"');
return `"${shortenedStyle}"`;
})
.join(',\n')
+ ']';
});
}
/**
* Remove every mention of `moduleId: module.id`.
* @param content {string} The source file's content.
* @returns {string} The content with all moduleId: mentions removed.
*/
function removeModuleId(content) {
return content.replace(/\s*moduleId:\s*module\.id\s*,?\s*/gm, '');
}
module.exports = inlineResources;
module.exports.inlineResourcesFromString = inlineResourcesFromString;
他们将其用作 gulp 任务的一部分,我已将我的脚本改写为简单的节点脚本。
我在构建中所做的是:
- 从我的 src 文件夹创建一个 dist 文件夹(因此 html 和 css 文件保持相对),然后从 dist 中:
- 如果需要,可以在此处进行任何类型的 postcss 处理(使用 postcss 最好的选择是使用替换标志来维护文件夹结构)
- 运行 内联器(上面的脚本)
- 删除 html 和 css 文件(不再需要它们)
- 使用 ngc 转译
在第一步中,我开始使用一个暂存文件夹,然后转译到一个 dist 文件夹,但是 ngc 并没有与 outDir 一起玩。
我花了一些时间才弄明白这一点,希望我能为您节省一些时间。