s-KaiNet / spfx-fast-serve

Improve your SharePoint Framework development flow by speeding up the "serve" command :rocket:
MIT License
133 stars 11 forks source link

Can't resolve 'fs' when using spfx-fast-serve with handlerbar #104

Closed kavaghela closed 1 year ago

kavaghela commented 1 year ago

I have created SPFx web part with version 1.15.2 and configured handlebar to generate render data based on HTML template (Similar way how PnP.Search web part is doing).

After configuring handle bar, spfx-fast-serve stopped working and it's throwing below error. However gulp-serve command is working without any issue.

ERROR in ./node_modules/handlebars-helpers/lib/fs.js Module not found: Error: Can't resolve 'fs' in '...\node_modules\handlebars-helpers\lib'

ERROR in ./node_modules/handlebars-helpers/lib/code.js Module not found: Error: Can't resolve 'fs' in '...\node_modules\handlebars-helpers\lib'

ERROR in ./node_modules/helper-md/index.js Module not found: Error: Can't resolve 'fs' in '...\node_modules\helper-md'

ERROR in ./node_modules/log-utils/index.js Module not found: Error: Can't resolve 'readline' in '...\node_modules\log-utils' i 「wdm」: Failed to compile.

I took the reference from pnp.search and did following changed in gulpfile.js

'use strict';
const fs = require('fs');
const build = require('@microsoft/sp-build-web');

build.addSuppression(`Warning - [sass] The local CSS class 'ms-Grid' is not camelCase and will not be type-safe.`);

const envCheck = build.subTask('environmentCheck', (gulp, config, done) => {

  if (!config.production) {
      //https://spblog.net/post/2019/09/18/spfx-overclockers-or-how-to-significantly-improve-your-sharepoint-framework-build-performance#h_296972879501568737888136      
      build.tslintCmd.enabled = false;
  }

  build.configureWebpack.mergeConfig({
      additionalConfiguration: (generatedConfiguration) => {

          generatedConfiguration.resolve.alias = { handlebars: 'handlebars/dist/handlebars.min.js' };

          generatedConfiguration.node = {
              fs: 'empty'
          }

          generatedConfiguration.module.rules.push({
              test: /utils\.js$/,
              loader: 'unlazy-loader',
              include: [
                  /node_modules/,
              ]
          }, {
              // Skip logging helpers as they break on webpack and are not needed
              test: /index.js$/,
              loader: 'string-replace-loader',
              include: [
                  /handlebars-helpers/,
              ],
              options: {
                  search: '(logging|markdown): require.*?,',
                  replace: '',
                  flags: 'g'
              }
          });

          generatedConfiguration.optimization.splitChunks.cacheGroups = { vendors: false };          

          return generatedConfiguration;
      }
  });

  done();
});

build.rig.addPreBuildTask(envCheck);
var getTasks = build.rig.getTasks;
build.rig.getTasks = function () {
  var result = getTasks.call(build.rig);

  result.set('serve', result.get('serve-deprecated'));

  return result;
};

/* fast-serve */
const { addFastServe } = require("spfx-fast-serve-helpers");
addFastServe(build);
/* end of fast-serve */

build.initialize(require('gulp'));`

Below is my package.json file:

{
  "name": "dynamicform",
  "version": "0.0.1",
  "private": false,
  "main": "lib/index.js",
  "scripts": {
    "build": "gulp bundle",
    "clean": "gulp clean",
    "test": "gulp test",
    "serve": "gulp bundle --custom-serve --max_old_space_size=4096 && fast-serve"
  },
  "dependencies": {
    "@fluentui/react": "^8.98.3",
    "@microsoft/sp-core-library": "1.15.2",
    "@microsoft/sp-lodash-subset": "1.15.2",
    "@microsoft/sp-office-ui-fabric-core": "1.15.2",
    "@microsoft/sp-property-pane": "1.15.2",
    "@microsoft/sp-webpart-base": "1.15.2",
    "@pnp/sp": "^3.16.0",
    "@webcomponents/custom-elements": "^1.4.3",
    "@webcomponents/webcomponentsjs": "^2.5.0",
    "clone-deep": "^4.0.1",
    "handlebars": "^4.7.7",
    "handlebars-helpers": "^0.10.0",
    "office-ui-fabric-react": "7.185.7",
    "react": "16.13.1",
    "react-dom": "16.13.1",
    "string-replace-loader": "^3.1.0",
    "tslib": "2.3.1"
  },
  "devDependencies": {
    "@microsoft/eslint-config-spfx": "1.15.2",
    "@microsoft/eslint-plugin-spfx": "1.15.2",
    "@microsoft/rush-stack-compiler-4.5": "0.2.2",
    "@microsoft/sp-build-web": "1.15.2",
    "@microsoft/sp-module-interfaces": "1.15.2",
    "@rushstack/eslint-config": "2.5.1",
    "@types/handlebars-helpers": "^0.5.3",
    "@types/react": "16.9.51",
    "@types/react-dom": "16.9.8",
    "@types/webpack-env": "~1.15.2",
    "ajv": "^6.12.5",
    "eslint-plugin-react-hooks": "4.3.0",
    "gulp": "4.0.2",
    "spfx-fast-serve-helpers": "~1.15.0",
    "typescript": "4.5.5",
    "unlazy-loader": "^0.1.3"
  },
  "browser": {
    "fs": false
  }
}

Below is my template service (again took hint from PnP.Search web part and took only necessary things)

import { WebPartContext } from "@microsoft/sp-webpart-base";
import * as Handlebars from "handlebars";
import { IComponentDefinition } from "../../models/IComponentDefinition";
import * as handlebarsHelpers from 'handlebars-helpers';
export class TemplateService {
    private handlebars: typeof Handlebars;
    private context: WebPartContext;

    constructor(context: WebPartContext, webComponents: IComponentDefinition<any>[]) {
        this.context = context;

        this.handlebars = Handlebars.create();
        this.registerWebComponents(webComponents);

    }
    private registerWebComponents(webComponents: IComponentDefinition<any>[]) {
        // Registers custom HTML elements
        if (webComponents && webComponents.length > 0) {
            webComponents.map(wc => {
                const component = customElements.get(wc.componentName);
                if (!component) {
                    customElements.define(wc.componentName, wc.componentClass);
                }
            });
        }

    }  
    private registerHelpers() {
        const helpers = handlebarsHelpers();

        // Registers all helpers in the global Handlebars context
        Object.keys(helpers).forEach(helperName => {
            this.handlebars.registerHelper(helperName, helpers[helperName]);
        });
    }

    public processTemplate(templateContext: any, templateContent: string) {
        const template = this.handlebars.compile(templateContent);
        return template(templateContext);
    }
}

Any solution to this issue?

s-KaiNet commented 1 year ago

You should also apply the same configuration to fast-serve via webpack extensibility. Actually I already did it before for search web parts and you can simply copy the config from there

kavaghela commented 1 year ago

@s-KaiNet Thank you very much for quick response and it worked. 👍

I am not sure why I only skipped that important file when I was taking reference from PnP.Search.

s-KaiNet commented 1 year ago

Great, then I'm closing the issue.