pcf-scripts
Version:
This package contains a module for building PowerApps Component Framework (PCF) controls. See project homepage how to install.
151 lines (149 loc) • 7.02 kB
JavaScript
;
// Copyright (C) Microsoft Corporation. All rights reserved.
Object.defineProperty(exports, "__esModule", { value: true });
exports.getNamespaceStub = getNamespaceStub;
exports.generateStub = generateStub;
exports.getWebpackConfig = getWebpackConfig;
const fs = require("node:fs");
const path = require("node:path");
const webpack_1 = require("webpack");
const webpack_merge_1 = require("webpack-merge");
const constants = require("./constants");
const featureManager_1 = require("./featureManager");
const platformLibrariesHandler_1 = require("./platformLibrariesHandler");
const buildConfig_1 = require("./buildConfig");
const featureMgr = new featureManager_1.FeatureManager();
const buildConfig = (0, buildConfig_1.getBuildConfig)();
// Append a stub to webpack bundle to prevent overwriting global variables
// If different controls are using the same namespace, webpack will keep redeclaring
// the namespace as global variables. As a result, only of one the controls can be called.
// The inserted stub checks whether the namespace already exists and uses a temporary variable
// to hold the control's constructor.
function getNamespaceStub(namespace, constructor) {
const splitNamespace = namespace.split(".");
let stub = `\tvar ${splitNamespace[0]} = ${splitNamespace[0]} || {};\n`;
for (let i = 1; i < splitNamespace.length; i++) {
const littleStub = `${splitNamespace.slice(0, i + 1).join(".")}`;
stub += `\t${littleStub} = ${littleStub} || {};\n`;
}
stub = stub + `\t${namespace}.${constructor} = ${constants.TEMP_NAMESPACE}.${constructor};\n` + `\t${constants.TEMP_NAMESPACE} = undefined;\n`;
return stub;
}
// Use registration function if exists, else fall back to the stub that uses the namespace as a global variable
function generateStub(namespace, constructor) {
return ("\nif (window.ComponentFramework && window.ComponentFramework.registerControl) {\n" +
`\tComponentFramework.registerControl('${namespace}.${constructor}', ${constants.TEMP_NAMESPACE}.${constructor});\n` +
`} else {\n${getNamespaceStub(namespace, constructor)}}`);
}
function getWebpackConfig(control, controlOutputDir, buildMode, watchFlag) {
const entryPoint = path.resolve(control.getControlPath(), control.getCodeRelativePath());
let customConfig = {};
const customConfigPath = path.resolve(control.getControlPath(), "..", constants.WEBPACK_CUSTOMIZATION_FILE_NAME);
if (featureMgr.isFeatureEnabled("pcfAllowCustomWebpack") && fs.existsSync(customConfigPath)) {
// eslint-disable-next-line @typescript-eslint/no-require-imports
customConfig = require(customConfigPath);
}
const allowProjectReferences = featureMgr.isFeatureEnabled("pcfAllowProjectReferences");
const babelLoader = getBabelLoader(buildMode === "production" ? true : "auto");
const oobConfig = {
// `production` mode will minify, while `development` will optimize for debugging.
mode: buildMode,
watch: watchFlag,
watchOptions: {
aggregateTimeout: 500,
ignored: /node_modules/,
},
// Tells webpack where to start walking the graph of dependencies
entry: entryPoint,
output: {
// This library value control what global variable the output control is placed in.
library: constants.TEMP_NAMESPACE,
pathinfo: true,
filename: constants.BUNDLE_NAME,
path: controlOutputDir,
},
resolve: {
// Tell webpack which extensions to try when it is looking for a file.
extensions: [".ts", ".tsx", ".mts", ".js", ".jsx", ".mjs", ".svg"],
},
module: {
rules: [
{
// Tells webpack how to load files with TS, TSX, MTS, or MTSX extensions.
test: /\.(tsx?|mtsx?)$/,
use: [
babelLoader,
{
loader: require.resolve("ts-loader"),
options: {
allowTsInNodeModules: true,
projectReferences: allowProjectReferences,
},
},
],
exclude: (tsPath) => {
// PNPM places relative imports in `node_modules`.
// These files should NOT be excluded from the loader.
// Format: node_modules/.pnpm/.../some/path/...@file+...
if (/node_modules.\.pnpm.+@file\+/.test(tsPath)) {
return false;
}
return tsPath.includes("node_modules");
},
},
{
// Tell webpack how to handle JS, JSX, MJS, or MJSX files
test: /\.(jsx?|mjsx?)$/,
use: [babelLoader],
},
{
test: /\.css$/,
use: ["style-loader", "css-loader", "sass-loader"],
},
{
test: /\.svg$/,
use: ["@svgr/webpack"],
},
],
},
plugins: [
new webpack_1.optimize.LimitChunkCountPlugin({
// prevent creating split bundles, since the PCF runtime cannot handle chunked bundles
// neither does the control manifest and our tooling have support to build and package chunked bundles (e.g. no SoPa support)
maxChunks: 1,
}),
new webpack_1.WatchIgnorePlugin({
paths: [/\.js$/, /\.d\.[cm]?ts$/],
}),
],
};
if (featureMgr.isFeatureEnabled("pcfReactControls") && (0, platformLibrariesHandler_1.hasPlatformLibs)(control)) {
const platformLibrariesHandler = new platformLibrariesHandler_1.PlatformLibrariesHandler();
const externalsForPlatformLibs = platformLibrariesHandler.getLatestVersions(control);
oobConfig.externals = externalsForPlatformLibs;
}
return (0, webpack_merge_1.merge)(oobConfig, customConfig);
}
// Some babel plugins to support modern JS and TypeScript.
const babelPlugins = [[require.resolve("@babel/plugin-proposal-decorators"), { version: "2023-11" }]];
// Config for babel to tell it about which browsers we are targeting.
const babelPresetEnv = [
require.resolve("@babel/preset-env"),
{
targets: buildConfig?.targets ?? {
esmodules: true,
},
},
];
function getBabelLoader(compact = "auto") {
return {
loader: require.resolve("babel-loader"),
options: {
sourceType: "unambiguous",
presets: [babelPresetEnv, [require.resolve("@babel/preset-react")]],
plugins: babelPlugins,
compact,
},
};
}
//# sourceMappingURL=webpackConfig.js.map