【问题标题】:Build Angular module with rollup.js: external html template file won't work (404)使用 rollup.js 构建 Angular 模块:外部 html 模板文件不起作用 (404)
【发布时间】:2018-08-08 23:55:44
【问题描述】:

我正在开发一个 Angular 库 (GitHub repo link),有

  • lib 模块源位于./src
  • 测试应用程序源放置在./app
  • lib 分布于./dist

构建过程使用 rollup.js 并基于 angular-library-starter。 我还有一个从./dist 生成npm 包并将其安装到./node_modules 的进程。问题是该应用程序适用于从./src 导入的lib 模块,而当我从./dist./node_modules 导入它时它不起作用:

// ./app/app/app.module.ts
import { AppComponent } from './app.component';
import { UiScrollModule } from '../../src/ngx-ui-scroll';   // works
//import { UiScrollModule } from 'ngx-ui-scroll';           // doesn't work
//import { UiScrollModule } from '../../dist/bundles/ngx-ui-scroll.umd.js'; // no...

@NgModule({
  declarations: [AppComponent],
  imports: [BrowserModule, UiScrollModule],
  bootstrap: [AppComponent]
})
export class AppModule { }

在构建过程中没有错误,但是浏览器控制台显示(对于./dist./node_modules 导入情况):

GET http://localhost:4200/ui-scroll.component.html 404(未找到)
加载ui-scroll.component.html失败

确实,我的 lib 模块有一个具有外部模板的组件:

// ./src/component/ui-scroll.component.ts
@Component({
  selector: 'app-ui-scroll',
  templateUrl: './ui-scroll.component.html'
})
export class UiScrollComponent implements OnInit, OnDestroy { /* ... */ }

如果我要内联模板(在@Component 设置中使用template 而不是templateUrl),它会起作用,我对其进行了测试。但我希望模板位于单独的文件中......我相信这是构建过程的责任。有一堆与我的构建过程相关的配置,我认为在这里列出它们并不是一个好主意。它们可以在lib repository 找到(或者我可以根据要求发布任何确切的部分)。

有没有人遇到过外部html模板rollup-bundling的问题?

【问题讨论】:

    标签: angular typescript rollupjs angular-library


    【解决方案1】:

    我相信这是构建过程的责任。

    看来你是对的。我们通常在构建过程中内联模板。

    为了做到这一点,您可以创建 js 文件,如:

    /utils/inline-resouces.js

    const {dirname, join} = require('path');
    const {readFileSync, writeFileSync} = require('fs');
    const glob = require('glob');
    
    /** Finds all JavaScript files in a directory and inlines all resources of Angular components. */
    module.exports = function inlineResourcesForDirectory(folderPath) {
      glob.sync(join(folderPath, '**/*.js')).forEach(filePath => inlineResources(filePath));
    };
    
    /** Inlines the external resources of Angular components of a file. */
    function inlineResources(filePath) {
      let fileContent = readFileSync(filePath, 'utf-8');
    
      fileContent = inlineTemplate(fileContent, filePath);
      fileContent = inlineStyles(fileContent, filePath);
    
      writeFileSync(filePath, fileContent, 'utf-8');
    }
    
    /** Inlines the templates of Angular components for a specified source file. */
    function inlineTemplate(fileContent, filePath) {
      return fileContent.replace(/templateUrl:\s*'([^']+?\.html)'/g, (_match, templateUrl) => {
        const templatePath = join(dirname(filePath), templateUrl);
        const templateContent = loadResourceFile(templatePath);
    
        return `template: "${templateContent}"`;
      });
    }
    
    /** Inlines the external styles of Angular components for a specified source file. */
    function inlineStyles(fileContent, filePath) {
      return fileContent.replace(/styleUrls:\s*(\[[\s\S]*?])/gm, (_match, styleUrlsValue) => {
        // The RegExp matches the array of external style files. This is a string right now and
        // can to be parsed using the `eval` method. The value looks like "['AAA.css', 'BBB.css']"
        const styleUrls = eval(styleUrlsValue);
    
        const styleContents = styleUrls
          .map(url => join(dirname(filePath), url))
          .map(path => loadResourceFile(path));
    
        return `styles: ["${styleContents.join(' ')}"]`;
      });
    }
    
    /** Loads the specified resource file and drops line-breaks of the content. */
    function loadResourceFile(filePath) {
      return readFileSync(
          filePath.replace('dist\\package\\esm5\\', '').replace('dist\\', ''), 'utf-8')
        .replace(/([\n\r]\s*)+/gm, ' ')
        .replace(/"/g, '\\"');
    }
    

    然后按如下方式更改您的build.js 文件:

    build.js

    ...
    const ESM5_DIR = `${NPM_DIR}/esm5`;
    const BUNDLES_DIR = `${NPM_DIR}/bundles`;
    const OUT_DIR_ESM5 = `${NPM_DIR}/package/esm5`;
    
    // 1) import function from created above file
    const inlineResourcesForDirectory = require('./utils/inline-resources');
    // 1) end
    
    ...
    
    /* AoT compilation */
    shell.echo(`Start AoT compilation`);
    if (shell.exec(`ngc -p tsconfig-build.json`).code !== 0) {
      shell.echo(chalk.red(`Error: AoT compilation failed`));
      shell.exit(1);
    }
    shell.echo(chalk.green(`AoT compilation completed`));
    
    // 2) Inline template after first ngc build
    shell.echo(`Start inlining templates in ${NPM_DIR} folder`);
    inlineResourcesForDirectory(NPM_DIR);
    shell.echo(`Inlining templates in ${NPM_DIR} folder completed`);
    // 2) end
    
    ...
    
    shell.echo(`Produce ESM5 version`);
    shell.exec(`ngc -p tsconfig-build.json --target es5 -d false --outDir ${OUT_DIR_ESM5} --importHelpers true --sourceMap`);
    
    // 3) Inline template after second ngc build
    shell.echo(`Start inlining templates in ${OUT_DIR_ESM5} folder`);
    inlineResourcesForDirectory(OUT_DIR_ESM5);
    shell.echo(`Inlining templates in ${OUT_DIR_ESM5} folder completed`);
    // 3) end
    
    if (shell.exec(`rollup -c rollup.es.config.js -i ${OUT_DIR_ESM5}/${PACKAGE}.js -o ${ESM5_DIR}/${PACKAGE}.js`).code !== 0) {
    

    完成上述所有 3 项更改后,您可以检查您的 ngx-ui-scroll.umd.js 捆绑包。它应该看起来像:

    另见

    【讨论】:

    • 这是一个很好的解决方案,但我认为它对我的小库来说太重了......我在StackOverflow 上找到了同样的问题并把my approach 放在那里。我也参考了你的回答...
    • 我明白了。感谢您的回复:) 角度材料团队使用了类似的方法
    猜你喜欢
    • 2017-02-17
    • 2020-03-14
    • 1970-01-01
    • 1970-01-01
    • 2021-01-10
    • 2020-09-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多