boenfu / vite-plugin-semi-theme

MIT License
12 stars 7 forks source link

[BUG]使用pnpm时,引入自定义主题会出现编译错误 #5

Open zk8080 opened 2 years ago

zk8080 commented 2 years ago

项目使用pnpm,引入自定义主题时,会出现编译错误。调试后发现是路径问题,pnpm下是软连接,导致这段代码解析出来的路径错误,能发增加一个路径参数进行兼容处理下呢🙏

new URL(
   url.substring(1),
   pathToFileURL(
     scssFilePath.match(/^(\S*\/node_modules\/)/)[0]
   )
)
HaceraI commented 2 years ago

老哥有找到解决方案吗?,我现在是直接直接把index.js Copy下来到自己的工程改了下代码,当作临时方案; 还有那个正则匹配不了带空格的目录:sweat_smile:

if (url.startsWith('~')) {
  const key = '/node_modules/';
  return new URL(
    url.substring(1),
    pathToFileURL(
      scssFilePath.substring(
        0,
        (url.startsWith('~@semi-bot') ? scssFilePath.indexOf(key) : scssFilePath.lastIndexOf(key)) + key.length
      )
    )
  );
}
zk8080 commented 2 years ago

老哥有找到解决方案吗?,我现在是直接直接把index.js Copy下来到自己的工程改了下代码,当作临时方案; 还有那个正则匹配不了带空格的目录😅

if (url.startsWith('~')) {
  const key = '/node_modules/';
  return new URL(
    url.substring(1),
    pathToFileURL(
      scssFilePath.substring(
        0,
        (url.startsWith('~@semi-bot') ? scssFilePath.indexOf(key) : scssFilePath.lastIndexOf(key)) + key.length
      )
    )
  );
}

目前直接在项目里加载了全量的主题样式,没有使用插件了

yuanzhixiang commented 2 years ago

老哥,抓紧修复呀

HaceraI commented 2 years ago

建议直接换yarn

HaceraI commented 2 years ago

用pnpm得做配置才能正常使用 semi-ui 里面的 dependency (或者自己去add) 我当时遇到用不了 Icon 的问题,最后是直接妥协改 yarn 了 如果大家有什么方法的话也欢迎分享一下,提前感谢

image

HaceraI commented 2 years ago

然后我把临时修复的文件也给大家传一下吧,等官方修复后记得替换掉这个

plugins/SemiPlugin.js

/**
 * Clone From: https://github.com/boenfu/vite-plugin-semi-theme/blob/main/index.js
 *
 * PNPM 定制Semi DSM 替换 Plugin
 */
import FS from 'fs';
import Path from 'path';

import { pathToFileURL } from 'url';

import pkg from 'sass';
import { platform } from 'os';
const { compileString, Logger } = pkg;

/**
 * @type {(options:{
 *  theme: string;
 *  options?: {
 *    prefixCls?: string;
 *     variables?: {[key: string]: string | number};
 *    include?: string;
 *  };
 * })=>any}
 *
 * @note 阅读 webpack 版本代码的理解
 * 1. 解析 css 到对应 scss
 * 2. 替换 scss 内容
 * 3. 再构建成对应的 css
 */
export default function SemiPlugin({ theme, options = {} }) {
  return {
    name: 'semi-theme',
    enforce: 'post',
    load(id) {
      const filePath = normalizePath(id);
      if (options.include) {
        options.include = normalizePath(options.include);
      }
      // https://github.com/DouyinFE/semi-design/blob/main/packages/semi-webpack/src/semi-webpack-plugin.ts#L83
      if (/@douyinfe\/semi-(ui|icons|foundation)\/lib\/.+\.css$/.test(filePath)) {
        const scssFilePath = filePath.replace(/\.css$/, '.scss');

        // 目前只有 name
        // https://github.com/DouyinFE/semi-design/blob/04d17a72846dfb5452801a556b6e01f9b0e8eb9d/packages/semi-webpack/src/semi-webpack-plugin.ts#L23
        const semiSemiLoaderOptions = { name: theme };

        return compileString(
          // TODO (boen): 未解析 file query
          loader(FS.readFileSync(scssFilePath), {
            ...semiSemiLoaderOptions,
            ...options,
            variables: convertMapToString(options.variables || {})
          }),
          {
            importers: [
              {
                findFileUrl(url) {
                  if (url.startsWith('~')) {
                    const key = '/node_modules/';
                    return new URL(
                      url.substring(1),
                      pathToFileURL(
                        scssFilePath.substring(
                          0,
                          (url.startsWith('~@semi-bot') ? scssFilePath.indexOf(key) : scssFilePath.lastIndexOf(key)) +
                            key.length
                        )
                      )
                    );
                  }

                  const filePath = Path.resolve(Path.dirname(scssFilePath), url);

                  if (FS.existsSync(filePath)) {
                    return pathToFileURL(filePath);
                  }

                  return null;
                }
              }
            ],
            logger: Logger.silent
          }
        ).css;
      }
    }
  };
}

// copy from https://github.com/DouyinFE/semi-design/blob/main/packages/semi-webpack/src/semi-theme-loader.ts
function loader(source, options) {
  let fileStr = source.toString('utf8');

  const theme = options.name || '@douyinfe/semi-theme-default';
  // always inject
  const scssVarStr = `@import "~${theme}/scss/index.scss";\n`;
  // inject once
  const cssVarStr = `@import "~${theme}/scss/global.scss";\n`;
  // [vite-plugin]: sync from https://github.com/DouyinFE/semi-design/commit/a6064489a683495a737cbe7abd72c0b49a3bcd06
  let animationStr = `@import "~${theme}/scss/animation.scss";\n`;

  try {
    require.resolve(`${theme}/scss/animation.scss`);
  } catch (e) {
    animationStr = ''; // fallback to empty string
  }

  const shouldInject = fileStr.includes('semi-base');

  let componentVariables;

  try {
    componentVariables = resolve.sync(this.context, `${theme}/scss/local.scss`);
  } catch (e) {}

  if (options.include || options.variables || componentVariables) {
    let localImport = '';
    if (componentVariables) {
      localImport += `\n@import "~${theme}/scss/local.scss";`;
    }
    if (options.include) {
      localImport += `\n@import "${options.include}";`;
    }
    if (options.variables) {
      localImport += `\n${options.variables}`;
    }
    try {
      const regex = /(@import '.\/variables.scss';?|@import ".\/variables.scss";?)/g;
      const fileSplit = fileStr.split(regex).filter((item) => Boolean(item));
      if (fileSplit.length > 1) {
        fileSplit.splice(fileSplit.length - 1, 0, localImport);
        fileStr = fileSplit.join('');
      }
    } catch (error) {}
  }

  // inject prefix
  const prefixCls = options.prefixCls || 'semi';

  const prefixClsStr = `$prefix: '${prefixCls}';\n`;

  if (shouldInject) {
    return `${animationStr}${cssVarStr}${scssVarStr}${prefixClsStr}${fileStr}`;
  } else {
    return `${scssVarStr}${prefixClsStr}${fileStr}`;
  }
}

// copy from https://github.com/DouyinFE/semi-design/blob/main/packages/semi-webpack/src/semi-webpack-plugin.ts#L136
function convertMapToString(map) {
  return Object.keys(map).reduce(function (prev, curr) {
    return prev + `${curr}: ${map[curr]};\n`;
  }, '');
}

function normalizePath(id) {
  return Path.posix.normalize(platform() === 'win32' ? id.replace(/\\/g, '/') : id);
}

用的时候注意import改成自己文件的: image

yuanzhixiang commented 2 years ago

yuanzhixiang commented 2 years ago

然后我把临时修复的文件也给大家传一下吧,等官方修复后记得替换掉这个

plugins/SemiPlugin.js

/**
 * Clone From: https://github.com/boenfu/vite-plugin-semi-theme/blob/main/index.js
 *
 * PNPM 定制Semi DSM 替换 Plugin
 */
import FS from 'fs';
import Path from 'path';

import { pathToFileURL } from 'url';

import pkg from 'sass';
import { platform } from 'os';
const { compileString, Logger } = pkg;

/**
 * @type {(options:{
 *  theme: string;
 *  options?: {
 *    prefixCls?: string;
 *     variables?: {[key: string]: string | number};
 *    include?: string;
 *  };
 * })=>any}
 *
 * @note 阅读 webpack 版本代码的理解
 * 1. 解析 css 到对应 scss
 * 2. 替换 scss 内容
 * 3. 再构建成对应的 css
 */
export default function SemiPlugin({ theme, options = {} }) {
  return {
    name: 'semi-theme',
    enforce: 'post',
    load(id) {
      const filePath = normalizePath(id);
      if (options.include) {
        options.include = normalizePath(options.include);
      }
      // https://github.com/DouyinFE/semi-design/blob/main/packages/semi-webpack/src/semi-webpack-plugin.ts#L83
      if (/@douyinfe\/semi-(ui|icons|foundation)\/lib\/.+\.css$/.test(filePath)) {
        const scssFilePath = filePath.replace(/\.css$/, '.scss');

        // 目前只有 name
        // https://github.com/DouyinFE/semi-design/blob/04d17a72846dfb5452801a556b6e01f9b0e8eb9d/packages/semi-webpack/src/semi-webpack-plugin.ts#L23
        const semiSemiLoaderOptions = { name: theme };

        return compileString(
          // TODO (boen): 未解析 file query
          loader(FS.readFileSync(scssFilePath), {
            ...semiSemiLoaderOptions,
            ...options,
            variables: convertMapToString(options.variables || {})
          }),
          {
            importers: [
              {
                findFileUrl(url) {
                  if (url.startsWith('~')) {
                    const key = '/node_modules/';
                    return new URL(
                      url.substring(1),
                      pathToFileURL(
                        scssFilePath.substring(
                          0,
                          (url.startsWith('~@semi-bot') ? scssFilePath.indexOf(key) : scssFilePath.lastIndexOf(key)) +
                            key.length
                        )
                      )
                    );
                  }

                  const filePath = Path.resolve(Path.dirname(scssFilePath), url);

                  if (FS.existsSync(filePath)) {
                    return pathToFileURL(filePath);
                  }

                  return null;
                }
              }
            ],
            logger: Logger.silent
          }
        ).css;
      }
    }
  };
}

// copy from https://github.com/DouyinFE/semi-design/blob/main/packages/semi-webpack/src/semi-theme-loader.ts
function loader(source, options) {
  let fileStr = source.toString('utf8');

  const theme = options.name || '@douyinfe/semi-theme-default';
  // always inject
  const scssVarStr = `@import "~${theme}/scss/index.scss";\n`;
  // inject once
  const cssVarStr = `@import "~${theme}/scss/global.scss";\n`;
  // [vite-plugin]: sync from https://github.com/DouyinFE/semi-design/commit/a6064489a683495a737cbe7abd72c0b49a3bcd06
  let animationStr = `@import "~${theme}/scss/animation.scss";\n`;

  try {
    require.resolve(`${theme}/scss/animation.scss`);
  } catch (e) {
    animationStr = ''; // fallback to empty string
  }

  const shouldInject = fileStr.includes('semi-base');

  let componentVariables;

  try {
    componentVariables = resolve.sync(this.context, `${theme}/scss/local.scss`);
  } catch (e) {}

  if (options.include || options.variables || componentVariables) {
    let localImport = '';
    if (componentVariables) {
      localImport += `\n@import "~${theme}/scss/local.scss";`;
    }
    if (options.include) {
      localImport += `\n@import "${options.include}";`;
    }
    if (options.variables) {
      localImport += `\n${options.variables}`;
    }
    try {
      const regex = /(@import '.\/variables.scss';?|@import ".\/variables.scss";?)/g;
      const fileSplit = fileStr.split(regex).filter((item) => Boolean(item));
      if (fileSplit.length > 1) {
        fileSplit.splice(fileSplit.length - 1, 0, localImport);
        fileStr = fileSplit.join('');
      }
    } catch (error) {}
  }

  // inject prefix
  const prefixCls = options.prefixCls || 'semi';

  const prefixClsStr = `$prefix: '${prefixCls}';\n`;

  if (shouldInject) {
    return `${animationStr}${cssVarStr}${scssVarStr}${prefixClsStr}${fileStr}`;
  } else {
    return `${scssVarStr}${prefixClsStr}${fileStr}`;
  }
}

// copy from https://github.com/DouyinFE/semi-design/blob/main/packages/semi-webpack/src/semi-webpack-plugin.ts#L136
function convertMapToString(map) {
  return Object.keys(map).reduce(function (prev, curr) {
    return prev + `${curr}: ${map[curr]};\n`;
  }, '');
}

function normalizePath(id) {
  return Path.posix.normalize(platform() === 'win32' ? id.replace(/\\/g, '/') : id);
}

用的时候注意import改成自己文件的: image

老哥,建议你提个 pull reqeust

xudaolong commented 1 month ago

然后我把临时修复的文件也给大家传一下吧,等官方修复后记得替换掉这个

plugins/SemiPlugin.js

/**
 * Clone From: https://github.com/boenfu/vite-plugin-semi-theme/blob/main/index.js
 *
 * PNPM 定制Semi DSM 替换 Plugin
 */
import FS from 'fs';
import Path from 'path';

import { pathToFileURL } from 'url';

import pkg from 'sass';
import { platform } from 'os';
const { compileString, Logger } = pkg;

/**
 * @type {(options:{
 *  theme: string;
 *  options?: {
 *    prefixCls?: string;
 *     variables?: {[key: string]: string | number};
 *    include?: string;
 *  };
 * })=>any}
 *
 * @note 阅读 webpack 版本代码的理解
 * 1. 解析 css 到对应 scss
 * 2. 替换 scss 内容
 * 3. 再构建成对应的 css
 */
export default function SemiPlugin({ theme, options = {} }) {
  return {
    name: 'semi-theme',
    enforce: 'post',
    load(id) {
      const filePath = normalizePath(id);
      if (options.include) {
        options.include = normalizePath(options.include);
      }
      // https://github.com/DouyinFE/semi-design/blob/main/packages/semi-webpack/src/semi-webpack-plugin.ts#L83
      if (/@douyinfe\/semi-(ui|icons|foundation)\/lib\/.+\.css$/.test(filePath)) {
        const scssFilePath = filePath.replace(/\.css$/, '.scss');

        // 目前只有 name
        // https://github.com/DouyinFE/semi-design/blob/04d17a72846dfb5452801a556b6e01f9b0e8eb9d/packages/semi-webpack/src/semi-webpack-plugin.ts#L23
        const semiSemiLoaderOptions = { name: theme };

        return compileString(
          // TODO (boen): 未解析 file query
          loader(FS.readFileSync(scssFilePath), {
            ...semiSemiLoaderOptions,
            ...options,
            variables: convertMapToString(options.variables || {})
          }),
          {
            importers: [
              {
                findFileUrl(url) {
                  if (url.startsWith('~')) {
                    const key = '/node_modules/';
                    return new URL(
                      url.substring(1),
                      pathToFileURL(
                        scssFilePath.substring(
                          0,
                          (url.startsWith('~@semi-bot') ? scssFilePath.indexOf(key) : scssFilePath.lastIndexOf(key)) +
                            key.length
                        )
                      )
                    );
                  }

                  const filePath = Path.resolve(Path.dirname(scssFilePath), url);

                  if (FS.existsSync(filePath)) {
                    return pathToFileURL(filePath);
                  }

                  return null;
                }
              }
            ],
            logger: Logger.silent
          }
        ).css;
      }
    }
  };
}

// copy from https://github.com/DouyinFE/semi-design/blob/main/packages/semi-webpack/src/semi-theme-loader.ts
function loader(source, options) {
  let fileStr = source.toString('utf8');

  const theme = options.name || '@douyinfe/semi-theme-default';
  // always inject
  const scssVarStr = `@import "~${theme}/scss/index.scss";\n`;
  // inject once
  const cssVarStr = `@import "~${theme}/scss/global.scss";\n`;
  // [vite-plugin]: sync from https://github.com/DouyinFE/semi-design/commit/a6064489a683495a737cbe7abd72c0b49a3bcd06
  let animationStr = `@import "~${theme}/scss/animation.scss";\n`;

  try {
    require.resolve(`${theme}/scss/animation.scss`);
  } catch (e) {
    animationStr = ''; // fallback to empty string
  }

  const shouldInject = fileStr.includes('semi-base');

  let componentVariables;

  try {
    componentVariables = resolve.sync(this.context, `${theme}/scss/local.scss`);
  } catch (e) {}

  if (options.include || options.variables || componentVariables) {
    let localImport = '';
    if (componentVariables) {
      localImport += `\n@import "~${theme}/scss/local.scss";`;
    }
    if (options.include) {
      localImport += `\n@import "${options.include}";`;
    }
    if (options.variables) {
      localImport += `\n${options.variables}`;
    }
    try {
      const regex = /(@import '.\/variables.scss';?|@import ".\/variables.scss";?)/g;
      const fileSplit = fileStr.split(regex).filter((item) => Boolean(item));
      if (fileSplit.length > 1) {
        fileSplit.splice(fileSplit.length - 1, 0, localImport);
        fileStr = fileSplit.join('');
      }
    } catch (error) {}
  }

  // inject prefix
  const prefixCls = options.prefixCls || 'semi';

  const prefixClsStr = `$prefix: '${prefixCls}';\n`;

  if (shouldInject) {
    return `${animationStr}${cssVarStr}${scssVarStr}${prefixClsStr}${fileStr}`;
  } else {
    return `${scssVarStr}${prefixClsStr}${fileStr}`;
  }
}

// copy from https://github.com/DouyinFE/semi-design/blob/main/packages/semi-webpack/src/semi-webpack-plugin.ts#L136
function convertMapToString(map) {
  return Object.keys(map).reduce(function (prev, curr) {
    return prev + `${curr}: ${map[curr]};\n`;
  }, '');
}

function normalizePath(id) {
  return Path.posix.normalize(platform() === 'win32' ? id.replace(/\\/g, '/') : id);
}

用的时候注意import改成自己文件的: image

使用注意把 resolve.sync(this.context, ${theme}/scss/local.scss); 改为

Path.resolve(`${theme}/scss/local.scss`);