【问题标题】:Webpack is not resolving extensions from third party librariesWebpack 不解析来自第三方库的扩展
【发布时间】:2017-08-06 13:33:31
【问题描述】:

我正在开发 2 个 Angular 2 库:

  1. carbonldp 工作台
  2. carbonldp 面板

Project2(面板):

是正在导出的组件和模块的集合。这些组件有自己的模板和 scss 文件。该库是使用 Typescript 编写的,但正在编译 dist 文件,因此该库作为 npm 包提供,带有自己的 .js 文件及其 .metadata.json.d.ts 文件。

Project1(工作台)

是主要项目。它使用 Project2 所以它可以工作。 这个项目使用 Webpack 来打包最终的应用程序。问题是我无法让 webpack 与 Project2 (Panel) 一起工作,因为主项目是用 typescript 编写的,而 panel 仅使用 javascript 文件导入。 我从 webapack 得到的错误是无法找到 Project2 中的所有模板文件。见下文:

这就是最终的 app.js 是如何捆绑 Project2 库的,请注意来自 Project2 的 header 组件是如何导入模板文件的:

注意 webpack 如何从 ma​​in 项目 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 库的导入?

【问题讨论】:

    标签: angular typescript webpack sass-loader raw-loader


    【解决方案1】:

    根据我的经验,angular2-template-loader 不会深入研究 node_modules,因此 node_modules 中可能依赖它的库将不走运。

    我解决它的方法是将外部模板和 css 文件作为库构建步骤的一部分进行内联,并且我很大程度上基于 angular material 2 如何发布他们的库。然后 webpack - 在消费应用程序级别 - 不需要担心您的模板或样式表,因为它们已经准备好了。

    为了不使用链接,这里是他们的脚本,我不相信它:

    #!/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 编译

    在第一步中,我首先使用 staging 文件夹,然后转译到 dist 文件夹,但 ngc 并没有与 outDir 一起播放。

    解决这个问题需要一些时间,所以希望我可以为您节省一些时间。

    【讨论】:

    • 谢谢你!!!!这实际上是我一直在寻找的!一种包含来自另一个库的文件内容的方法。这就是答案。
    • 另外,如果 angular-router-loader 也能做到这一点,您是否碰巧知道?这个库包含延迟加载的路由,我实际上得到了相同的结果。尝试加载 module.ts 文件时出现“404”... 检查捆绑文件时,Webpack 正确更改了 loadChildren 路由,但与库相比,它保留了完整的路径,即普通的旧字符串。我的意思是,这太荒谬了,不能将第三方完整的 Angular 库与 webpack 一起使用。
    • 实际上,这是一个非常好的问题。我不知道,我的项目还没有这样做,所以还没有遇到它。也许将其作为另一个 SO 问题发布,同时我会考虑一下。
    猜你喜欢
    • 2018-06-21
    • 2023-04-08
    • 2018-01-12
    • 2021-12-18
    • 2017-06-22
    • 1970-01-01
    • 1970-01-01
    • 2017-06-23
    • 2019-02-03
    相关资源
    最近更新 更多