sveltejs / svelte-loader

Webpack loader for svelte components.
MIT License
594 stars 73 forks source link

getting conditionNames required warning, but my config has conditionNames property in webpack config #226

Closed elirov closed 1 year ago

elirov commented 1 year ago

$ yarn watch yarn run v1.23.0-20220130.1630

WARNING: You should add "svelte" to the "resolve.conditionNames" array in your webpack config. See https://github.com/sveltejs/svelte-loader#resolveconditionnames for more information

webpack config file:

  config.resolve = {
    fallback: { assert: false },
    extensions: ['.cjs', '.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.css', '.scss', '.svelte'],
    modules: ['src', 'lib', 'node_modules', 'tmpls'],
    alias: {
      vendor: __dirname + '/lib/vendor',
      jquery: __dirname + '/lib/vendor-adapters/jquery-global-adapter',
      jqueryNonGlobal: __dirname + '/node_modules/jquery',
      moment: __dirname + '/lib/vendor-adapters/moment-global-adapter',
      momentNonGlobal: __dirname + '/node_modules/moment',
      xdoc: __dirname + '/src/xdoc',
      client: __dirname + '/src/client',
      node_modules: 'node_modules',
      test: __dirname + '/src/test',
      svelte: __dirname + '/node_modules/svelte',
      src: __dirname + '/src',
      tmpls: __dirname + '/tmpls',
    },
    conditionNames: ['svelte'],
  };

Any places I should check? Is the warning looking at the value of the property? Or is it trying to parse the config file itself?

dummdidumm commented 1 year ago

Could you post the complete config file (including where/how the export of it happens; you can redact any sensitive strings)? Check happens here and should find your settings, so that's strange

elirov commented 1 year ago
const webpack = require('webpack');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const fs = require('fs');
const path = require('path');
const { merge } = require('webpack-merge');
const crypto = require('crypto');
const uuidv4 = require('uuid/v4');
const FORM_KEY = 'f0';
const XDOC_CONTAINER_ID = 'XDocContainer-' + FORM_KEY;
const argv = require('yargs')(process.argv.slice(2)).argv;
const { processNestedHtmlFactory, addTemplateAliases } = require('./webpack-utils');
const buildStaticFields = require('./build-utils/xdoc-static-field-processor');
const buildDynamicFields = require('./build-utils/xdoc-static-to-dynamic-field-converter');
const { pipe } = require('lodash/fp');
const sveltePreprocess = require('svelte-preprocess');

const ROOT = path.resolve(__dirname);
const rootPath = path.join.bind(path, ROOT);

const SpeedMeasurePlugin = require('speed-measure-webpack-plugin');
const smp = new SpeedMeasurePlugin();

const getTmplConfig = async (env, tmplSettings) => {
  const tmplName = env.tmpl;
  let tmplDefinedSettings;
  try {
    //tmplDefinedSettings = require(rootPath(`/tmpls/${tmplName}/xdoc-platform-settings.mjs`)).default;
    tmplDefinedSettings = await import(rootPath(`/tmpls/${tmplName}/xdoc-platform-settings.mjs`));
  } catch (err) {
    console.log(`${tmplName} needs to provide tmpls/${tmplName}/xdoc-platform-settings.mjs`, err);
    throw err;
  }
  let fieldMapPath;
  if (fs.existsSync(`${__dirname}/tmpls/${tmplName}/fieldmap/fieldmap.js`)) {
    fieldMapPath = `tmpls/${tmplName}/fieldmap/fieldmap.js`;
  } else {
    fieldMapPath = `tmpls/${tmplName}/fieldMap.json`;
  }

  let fieldConverter;
  if (tmplDefinedSettings.build?.convertFieldsToDynamic) {
    console.log(`converting tmpls/${tmplName}/fieldMap.json static fields to dynamic fields`);
    fieldConverter = buildDynamicFields;
  } else {
    console.log(`NOT converting static fields defined in tmpls/${tmplName}/fieldMap.json`);
    fieldConverter = buildStaticFields;
  }
  const config = {
    infrastructureLogging: {
      level: 'warn',
    },
    entry: {},
    target: 'web',
    module: {
      rules: [
        {
          test: /\.html$/,
          use: [
            {
              loader: 'html-loader',
              options: {
                esModule: false,
                preprocessor: (content, loaderContext) => {
                  content = content.replace(/__IMPORT_TEMPLATE_PATH__/g, rootPath(`/tmpls/${tmplName}`));
                  const combineHtmlPreprocessor = processNestedHtmlFactory(rootPath, rootPath(`/tmpls/${tmplName}`));
                  const combined = combineHtmlPreprocessor(content, loaderContext);
                  return fieldConverter(combined, {
                    context: loaderContext,
                    fieldMapPath: rootPath(fieldMapPath),
                    formKey: FORM_KEY,
                  });
                },
              },
            },
          ],
        },
      ],
    },
    plugins: [
      new webpack.NormalModuleReplacementPlugin(/__IMPORT_TEMPLATE_PATH__/, (res) => {
        res.request = res.request.replace(/__IMPORT_TEMPLATE_PATH__/g, `${__dirname}/tmpls/${tmplName}`);
      }),
      new webpack.NormalModuleReplacementPlugin(/__IMPORT_FIELDMAP_PATH__/, (res) => {
        res.request = res.request.replace(/__IMPORT_FIELDMAP_PATH__/g, fieldMapPath);
      }),
      new webpack.NormalModuleReplacementPlugin(/__IMPORT_TEMPLATE_INIT_SCRIPT__/, (res) => {
        res.request = res.request.replace(
          /__IMPORT_TEMPLATE_INIT_SCRIPT__/g,
          `${__dirname}/${tmplSettings.initScript}`
        );
      }),
      new webpack.NormalModuleReplacementPlugin(/__FORM_KEY__/, (res) => {
        res.request = res.request.replace(/__FORM_KEY__/g, FORM_KEY);
      }),
      new HtmlWebpackPlugin({
        inject: true,
        chunks: [tmplName],
        template: `src/client/dev/index.ejs`,
        filename: `${tmplName}.html`,
      }),
    ],
  };
  config.entry[tmplName] = `${__dirname}/src/client/dev/main.js`;
  return config;
};

const getMainConfig = (env, tmplSettings) => {
  var config = {};
  config.devtool = 'eval-cheap-module-source-map';
  config.mode = 'development';
  config.optimization = {
    usedExports: true,
  };
  config.resolve = {
    fallback: { assert: false },
    extensions: ['.cjs', '.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.css', '.scss', '.svelte'],
    modules: ['src', 'lib', 'node_modules', 'tmpls'],
    alias: {
      vendor: __dirname + '/lib/vendor',
      jquery: __dirname + '/lib/vendor-adapters/jquery-global-adapter',
      jqueryNonGlobal: __dirname + '/node_modules/jquery',
      moment: __dirname + '/lib/vendor-adapters/moment-global-adapter',
      momentNonGlobal: __dirname + '/node_modules/moment',
      xdoc: __dirname + '/src/xdoc',
      client: __dirname + '/src/client',
      node_modules: 'node_modules',
      test: __dirname + '/src/test',
      svelte: __dirname + '/node_modules/svelte',
      src: __dirname + '/src',
      tmpls: __dirname + '/tmpls',
    },
  };
  pipe(addTemplateAliases)(config.resolve.alias);
  // console.log('aliases = ', config.resolve.alias);
  config.resolveLoader = {
    modules: ['node_modules', path.resolve(__dirname, 'build-utils', 'loaders')],
  };
  config.output = {
    path: __dirname + '/dist/dev',
    filename: '[name].js',
  };
  config.module = {
    rules: [
      {
        test: /\.(svelte)$/,
        use: {
          loader: 'svelte-loader',
          options: {
            compilerOptions: {
              dev: true,
            },
            preprocess: sveltePreprocess({}),
            // ignore a11y errors
            onwarn: (warning, handler) => {
              if (warning.code.toLowerCase().startsWith('a11y-')) {
                return;
              }
              handler(warning);
            },
          },
        },
      },
      {
        test: /\.ts$/,
        exclude: /node_modules/,
        use: [
          {
            loader: 'esbuild-loader',
            options: {
              loader: 'ts',
              target: 'es2020',
              //onlyCompileBundledFiles: true,
              //transpileOnly: true,
            },
          },
        ],
      },
      {
        test: /\.c?m?jsx?$/,
        resolve: {
          fullySpecified: false,
        },
        use: { loader: 'esbuild-loader' },
        exclude: {
          and: [/node_modules/],
        },
      },
      {
        test: require.resolve('jquery'),
        use: [
          {
            loader: 'expose-loader',
            options: {
              exposes: ['jQuery', '$'],
            },
          },
        ],
      },
      {
        test: /\.dot$/,
        use: [
          {
            loader: 'raw-loader',
          },
        ],
      },
      {
        test: /\.scss$/,
        include: path.resolve(__dirname, 'src/client/dev'),
        use: [
          'style-loader',
          {
            loader: 'css-loader',
            options: { url: false, sourceMap: true },
          },
          {
            loader: 'sass-loader',
            options: {
              additionalData: `$templateId: '${tmplSettings.templateId}';`,
            },
          },
          {
            loader: 'xdoc-sass-loader',
            options: {
              tmplPath: `${__dirname}/tmpls/${env.tmpl}`,
            },
          },
        ],
      },
      {
        test: /\.scss$/,
        exclude: path.resolve(__dirname, 'src/client/dev'),
        use: [
          'style-loader',
          {
            loader: 'css-loader',
            options: { url: false, sourceMap: true },
          },
          {
            loader: 'sass-loader',
            options: {
              additionalData: `$templateId: '${tmplSettings.templateId}';`,
            },
          },
        ],
      },
      {
        test: /\.less$/,
        use: [
          'style-loader',
          {
            loader: 'css-loader',
            options: { url: false, sourceMap: true },
          },
          {
            loader: 'less-loader',
            options: { sourceMap: true },
          },
        ],
      },
      {
        test: /\.css$/,
        use: [
          'style-loader',
          {
            loader: 'css-loader',
            options: { sourceMap: true },
          },
        ],
      },
      {
        test: /\.woff2?(\?v=[0-9]\.[0-9]\.[0-9])?$/,
        use: 'url-loader?limit=10000',
      },
      {
        test: /\.(ttf|eot|svg)(\?[\s\S]+)?$/,
        use: 'file-loader',
      },
      {
        test: /\.(jpe?g|png|gif|svg)$/i,
        use: ['file-loader?name=images/[name].[ext]'],
      },
    ],
  };

  // optionally disables pdf printing if narrative styling is not used within the passed in template
  if (!fs.existsSync(`./tmpls/${env.tmpl}/narrative.scss`)) {
    config.module.rules.push({
      test: /pdf-print.js/,
      loader: 'null-loader',
    });
    if (env.tmpl && process.env.BABEL_ENV !== 'test') {
      console.log(`${env.tmpl} does not use narrative.scss. dev tool PDF printing disabled.`);
    }
  }

  config.plugins = [
    new CopyWebpackPlugin({
      patterns: [
        {
          from: __dirname + '/server',
          to: __dirname + '/dist/dev/server',
        },
        {
          from: __dirname + '/node_modules/material-design-icons-iconfont/dist/fonts',
          to: __dirname + '/dist/dev/server/global-resources/fonts',
        },
        {
          from: __dirname + '/node_modules/tinymce',
          to: __dirname + '/dist/dev/servlet/gwt/assets/tinymce',
        },
      ],
    }),
    new webpack.ProgressPlugin(),
    new webpack.ProvidePlugin({
      autosize: 'autosize',
      _: 'lodash',
      doT: 'dot',
    }),
    new webpack.DefinePlugin({
      __FORM_KEY: JSON.stringify(FORM_KEY),
      __XDOC_CONTAINER_ID: JSON.stringify(XDOC_CONTAINER_ID),
      __TEMPLATE_ID: JSON.stringify(tmplSettings.templateId),
      __TEMPLATE_HEADER_ID: JSON.stringify(tmplSettings.templateHeaderId),
      __TEMPLATE_BODY_ID: JSON.stringify(tmplSettings.templateBodyId),
      __TEMPLATE_RDE_NAME__: JSON.stringify(env.tmpl),
      __APP_ENV: "'DEV'",
      __API_BASE_URL: "'" + (argv.apiUrl ?? '/servlet') + "'",
    }),
  ];
  return config;
};

const buildTmplSettings = (env) => {
  const settings = {
    templateId: 'NA',
    templateHeaderId: undefined,
    templateBodyId: undefined,
    initScript: `tmpls/${env.tmpl}/init.js`,
  };

  // yarn test
  // yarn watch supernote
  // yarn watch without a template name - error

  if (!env.ideAnalyzer) {
    const hash = crypto.createHash('sha1');
    hash.update(fs.readFileSync(settings.initScript) + fs.readFileSync(`tmpls/${env.tmpl}/template.html`));
    settings.templateId = hash.digest('hex');
    settings.templateHeaderId = uuidv4().replace(/-/g, '');
    settings.templateBodyId = uuidv4().replace(/-/g, '');
  }

  return settings;
};

const knownEnvVars = ['WEBPACK_BUNDLE', 'WEBPACK_BUILD', 'WEBPACK_WATCH', 't', 'tmpl', 'template', '--template'];

function findTemplateName(env) {
  const envKeys = Object.keys(env);
  const unknownArguments = envKeys.filter((v) => !knownEnvVars.includes(v));
  if (unknownArguments.length === 1) {
    // if only one unknown arg, then it's the template name
    return unknownArguments[0];
  }
  return undefined;
}

module.exports = async (env) => {
  if (process.env.BABEL_ENV !== 'test') {
    env.tmpl = env.tmpl ?? env.template ?? env.t ?? findTemplateName(env);
  }
  if (!env.tmpl) {
    throw Error(`No template name given. Args given: ${Object.keys(env)}. use yarn watch TEMPLATE`);
  }
  if (env.tmpl === 'development') {
    env.ideAnalyzer = true;
  }
  const tmplSettings = buildTmplSettings(env);
  const webpackConfig = await getTmplConfig(env, tmplSettings);
  return merge(
    getMainConfig(env, tmplSettings),
    process.env.WATCH_STATS === 'on' ? smp.wrap(webpackConfig) : webpackConfig
  );
};
elirov commented 1 year ago

I guess the interesting part of this config is that we're returning an async function instead of a complete object. But that's because we need to do some async actions before we have our complete config. As far as I know this type of setup is supported by webpack.

elirov commented 1 year ago

Also, I see that we're depending on env, and it looks like the only way for us to get the "env" parameter is to have module.exports be assigned to a function as opposed to an object. see: https://webpack.js.org/guides/environment-variables/

dehmer commented 1 year ago

@elirov Your final config seems to be lacking conditionNames: ['svelte']. I was struggling with the same issue. But neither module.exports = env => {...} nor module.exports = env => [...] seem to be a problem. I verified this by playing with https://github.com/sveltejs/template-webpack template.

For me it was definitively the missing conditionNames: ['svelte']

🤞

dehmer commented 1 year ago

@elirov Sorry for the false information above! (env) => ... IS the problem. The template had an older version of svelte-loader. 3.1.5 barks if config returns a function. Maybe you can get rid of env by using "build": "cross-env NODE_ENV=production webpack" and const mode = process.env.NODE_ENV || 'development' as demonstrated in the template.

dehmer commented 1 year ago

Returning multiple configurations won't work either. I would need this for an Electron Build (main and renderer configs.)

At least, I would expect module.exports = [config] from working without warnings.

dehmer commented 1 year ago

@dummdidumm

All working flawlessly in 3.1.7: mode.export = config mode.export = [config] mode.export = env => config mode.export = env => [config]

Thank! 👍