首页
Search
1
Linux 下 Bash 脚本 bad interpreter 报错的解决方法
70 阅读
2
Arch Linux 下解决 KDE Plasma Discover 的 Unable to load applications 错误
52 阅读
3
Arch Linux 下解决 KDE Plasma Discover 的 Unable to load applications 错误
42 阅读
4
如何在 Clash for Windows 上配置服务
40 阅读
5
如何在 IOS Shadowrocket 上配置服务
40 阅读
clash
服务器
javascript
全部
游戏资讯
登录
Search
加速器之家
累计撰写
1,061
篇文章
累计收到
0
条评论
首页
栏目
clash
服务器
javascript
全部
游戏资讯
页面
搜索到
1061
篇与
的结果
2024-10-20
前端 web 已支持 randomUUID 方法
从 Chome92 版本开始,crypto 模块已经支持randomUUID()方法了。 1. 以前的方式 # 在前端 web 中,我们在生成 uuid 时,通常都会使用时间戳或 Math.random()。例如在 stackoverflow 上,有段很出名的代码:How to create a GUID / UUID,就是Math.random()来生成的:function uuidv4() { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { var r = (Math.random() * 16) | 0, v = c == 'x' ? r : (r & 0x3) | 0x8; return v.toString(16); }); } console.log(uuidv4()); // "f94adc58-89b7-4c70-bc92-2935ea6fb5d2" 格式一样,但每次都不一样 每个位置都调用一次 random()方法。 2. randomUUID # 现在,Chrome 从 92 版本开始,已经原生支持 uuId 方法了,即crypto.randomUUID()。crypto.randomUUID(); 以后想获取一个随机值,或者用来标识设备(要求不高的前提下)的时候,就可以用这个randomUUID()方法。 3. 总结 # randomUUID()这个浏览器提供的原生确实很好用,而且符合 v4 版本的格式。但就是有一个缺点:兼容性太差。不止其他浏览器不支持,即使在 Chrome 浏览器,也只有从 92 版本开始,才能使用。不过相信不久的将来,其他浏览器也会支持这个方法的。我们来写一个前端获取uuid的方法。 优先使用crypto.randomUUID; 没有的话,使用优先使用crypto.getRandomValues; 使用Math.random()进行兜底; const generateUUID = () => { if (typeof crypto === 'object') { if (typeof crypto.randomUUID === 'function') { // https://developer.mozilla.org/en-US/docs/Web/API/Crypto/randomUUID return crypto.randomUUID(); } if (typeof crypto.getRandomValues === 'function' && typeof Uint8Array === 'function') { // https://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid const callback = (c) => { const num = Number(c); return (num ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (num / 4)))).toString(16); }; return '10000000-1000-4000-8000-100000000000'.replace(/[018]/g, callback); } } let timestamp = new Date().getTime(); let perforNow = (typeof performance !== 'undefined' && performance.now && performance.now() * 1000) || 0; return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => { let random = Math.random() * 16; if (timestamp > 0) { random = (timestamp + random) % 16 | 0; timestamp = Math.floor(timestamp / 16); } else { random = (perforNow + random) % 16 | 0; perforNow = Math.floor(perforNow / 16); } return (c === 'x' ? random : (random & 0x3) | 0x8).toString(16); }); };
2024年10月20日
3 阅读
0 评论
0 点赞
2024-10-20
如何打造一个react技术栈的多页面的框架应用
现在很多脚手架基本上都是单页面入口的应用,比如create-react-app等,整个的入口就是src/index.js。即使是Next.js,可以使用next export,导出成一个个单独的 html 页面。但在编译时,依然是全量编译,作为本身是为同构直出量身定制的服务端框架,在导出成纯前端运行的页面时,会多出很多无用的代码。我已将改造完成的框架放在了GitHub上,您可以直接下载下来进行体验:https://github.com/wenzi0github/create-react-app-mpa。 1. 现状 # 我们业务的主要特点是: 更偏向于活动,每个活动都搭建一个脚手架和对接构建流水线,很麻烦; 每个活动 h5 都有单独定制的部分,且用户交互也多,只能人工开发; 希望把 Git 仓库进行聚合,避免分散在各个位置; 因此,这里我基于create-react-app进行改造,实现后的功能有: 命令行快速初始化一个项目或者页面; 统一代码规范,接入公司的 eslint 代码检查; 每次提交后,只构建当前发生修改的项目; 测试环境和预发布环境自动插入 vconsole 等调试工具和构建时间; 兼容之前已构建的 html 页面,因之前是采用纯 html 发布的,这里还要兼容一下的(我们部门独有的方式,各位可以忽略); 2. 改造流程 # 接下来按照我的步骤,一步步下来,就能顺利的搭建起来了。 2.1 创建项目 # 我们首先使用 create-react-app 初始化一个项目。我比较喜欢使用 typescript,因此可以:npx create-react-app my-app --template typescript #or yarn create react-app my-app --template typescript 进入到项目my-app中,因脚手架将配置隐藏了,改造时,需使用npm run eject命令释放出配置。 注意,这是一个单项操作,执行后所有的配置就不能再收回去了。 $npm run eject 执行后可以看到多了 config 和 scripts 两个文件夹,我们几乎所有的改动都在这两个里面。不同的版本,在配置上可能稍微有点区别。 2.2 添加 sass 和 less # sass 的配置是create-react-app本身已配置好,但还需要自己下载一下sass这个包:$npm i sass --save-dev #or $yarn add sass 对于 less,我们就需要自己配置一下,先安装 less 和 less-loader,然后直接参考 sass 的配置即可,在config/webpack.config.js中。安装 less:$npm install less less-loader --save-dev 配置:// config/webpack.config.js // 64行左右 const sassRegex = /\.(scss|sass)$/; const sassModuleRegex = /\.module\.(scss|sass)$/; + const lessRegex = /\.less$/; // 新增less的后缀名 + const lessModuleRegex = /\.module\.less$/; // 新增less的后缀名 // 538行左右 rules: [ // 紧接着sass的配置 { test: lessRegex, exclude: lessModuleRegex, use: getStyleLoaders( { importLoaders: 3, sourceMap: isEnvProduction ? shouldUseSourceMap : isEnvDevelopment, }, 'less-loader' ), sideEffects: true, }, { test: lessModuleRegex, use: getStyleLoaders( { importLoaders: 3, sourceMap: isEnvProduction ? shouldUseSourceMap : isEnvDevelopment, modules: { getLocalIdent: getCSSModuleLocalIdent, }, }, 'less-loader' ), }, ] 这样项目中 sass 和 less 都可以支持了。 2.3 多入口的配置 # 在config/paths.js中,有各种的配置,这里我们需要添加几个根据项目目录构建的配置。// config/paths.js module.exports = { // build后的目录 appProjectBuild: (project) => { if (project) { return resolveApp(`dist/${project.project}`); } return resolveApp('dist'); }, // 之前是指定的src/index的文件 // 现在是读取src/pages中的项目的文件名 appProjectIndexJs: (project) => resolveModule(resolveApp, `src/pages/${project.project}/${project.name}`), // 项目的入口文件 }; 这里我们添加了 2 个配置,一个是项目打包时的构建目录,一个是构建的入口文件。在config/webpack.config.js中的导出,是一个函数,这里是我们主要改造的地方。 按照传入的 project 设置 entery 字段和 output 字段; 添加postcss-px-to-viewport插件,将 px 转为 vw 单位; 设置 alias; 删除不必要的 service worker(如果需要的话,那可以不删除); 在多入口的配置上,本地开发和线上发布的是不一样的,这里我们通过变量 isEnvProduction 进行了区分。在本地开发过程中,只能同时启动一个项目,因此会把该项目中所有的页面都作为入口文件;而在发布的过程中,则是通过判断项目是否变动,来进行构建,因此这里没有限定构建项目,而是可以根据变动的项目,产生多个入口。 2.3.1 设置入口和出口路径 # 默认的 entry 和 output 都是固定的,我们要修改为与启动和打包的项目相关。// config/webpack.config.js // 添加第2参数project module.exports = function (webpackEnv, project) { /** * 获取入口 * @param {object} entryPages 当前项目所有的页面 * @returns {object} */ const getEntries = (entryPages) => { if (isEnvProduction) { return [paths.appProjectIndexJs(entryPages)]; } const entries = {}; entryPages.forEach((page) => { entries[page.name] = !shouldUseReactRefresh ? [webpackDevClientEntry, paths.appProjectIndexJs(page)] : paths.appProjectIndexJs(page); }); return entries; }; /** * 根据页面名称生成htmlplugins * @param {string[]} pages 项目所有的页面名称 * @returns {any[]} */ const getHtmlPlugins = (pages) => { if (isEnvProduction) { return [ new HtmlWebpackPlugin( Object.assign( {}, { title: pages.config.title, inject: true, template: paths.appHtml, filename: `${pages.name}.html`, compiledTime: new Date().toLocaleString(), projectname: pages.config.project, minify: { removeComments: true, collapseWhitespace: true, removeRedundantAttributes: true, useShortDoctype: true, removeEmptyAttributes: true, removeStyleLinkTypeAttributes: true, keepClosingSlash: true, minifyJS: true, minifyCSS: true, minifyURLs: true, }, }, ), ), ]; } return pages.map((page) => { return new HtmlWebpackPlugin( Object.assign( {}, { title: page.config.title, inject: true, projectname: page.config.project, template: paths.appHtml, filename: `${page.name}.html`, chunks: [page.name], }, ), ); }); }; return { entry: getEntries(project), output: { path: isEnvProduction ? paths.appProjectBuild(project) : undefined, }, }; }; 在 public/index.html 中,我们发现有使用到htmlWebpackPlugin.options.title的变量,这是使用HtmlWebpackPlugin插件定义的。我还自己定义了一个变量compiledTime,用来标识该项目的编译时间。然后我在 index.html 中作为 meta 标签来使用: 当然,有些同学喜欢用注释的方式,也是可以的: 2.3.2 添加postcss-px-to-viewport插件 # 我个人在开发中,喜欢使用vw和vh单位,来开发移动端的应用,那么postcss-px-to-viewport插件就是必然要安装的。$npm install postcss-px-to-viewport --save-dev 然后在postcss-loader中的 options.plugins 里添加上这个插件:// config/webpack.config.js // 读取viewportWidth的配置,这个后面要讲 // 正式环境是按照页面构建的,project表示是一个页面的配置 // 本地环境是按照项目构建的,project是页面的数组集合 const { viewportWidth } = isEnvProduction ? project.config : project[0].config; const getStyleLoaders = (cssOptions, preProcessor) => { const loaders = [ // 忽略其他代码 { loader: require.resolve('postcss-loader'), options: { // Necessary for external CSS imports to work // https://github.com/facebook/create-react-app/issues/2677 ident: 'postcss', plugins: () => [ require('postcss-flexbugs-fixes'), require('postcss-preset-env')({ autoprefixer: { flexbox: 'no-2009', }, stage: 3, }), + require('postcss-px-to-viewport')({ + viewportWidth: viewportWidth || 750, + unitPrecision: 3, + viewportUnit: 'vw', + }), // Adds PostCSS Normalize as the reset css with default options, // so that it honors browserslist config in package.json // which in turn let's users customize the target behavior as per their needs. postcssNormalize(), ], sourceMap: isEnvProduction ? shouldUseSourceMap : isEnvDevelopment, }, }, ]; // 忽略其他代码 }; 2.3.3 设置 alias # 我们可以设置根路径,避免每次都按照相对路径来进行引用。这里要配置 2 个文件,一个是 config/webpack.config.js,另一个是 tsconfig.json。config/webpack.config.js:// config/webpack.config.js // 直接查找resolve.alias的位置 module.exports = function (webpackEnv, project) { /* 忽略其他代码 */ return { resolve: { alias: { // Support React Native Web // https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/ 'react-native': 'react-native-web', + '@': path.resolve(__dirname, '..', 'src'), // 以src目录为起始的根目录 // Allows for better profiling with ReactDevTools ...(isEnvProductionProfile && { 'react-dom$': 'react-dom/profiling', 'scheduler/tracing': 'scheduler/tracing-profiling', }), ...(modules.webpackAliases || {}), }, }, }; }; tsconfig.json:{ "compilerOptions": { "baseUrl": ".", "paths": { "@/*": ["./src/*"] } } } 这样就可以通过@来引用了:import api from '@/common/config/api'; // 项目目录/src/common/config/api 2.3.4 删除不必要的代码 # 有些代码是我们项目中用不到的,这里进行了删除处理,如果您的项目用到的话,那就别删除了。删除 service worker:// Generate a service worker script that will precache, and keep up to date, // the HTML & assets that are part of the webpack build. isEnvProduction && fs.existsSync(swSrc) && new WorkboxWebpackPlugin.InjectManifest({ swSrc, dontCacheBustURLsMatching: /\.[0-9a-f]{8}\./, exclude: [/\.map$/, /asset-manifest\.json$/, /LICENSE/], // Bump up the default maximum size (2mb) that's precached, // to make lazy-loading failure scenarios less likely. // See https://github.com/cra-template/pwa/issues/13#issuecomment-722667270 maximumFileSizeToCacheInBytes: 5 * 1024 * 1024, }), 2.3.5 关于 manifest 的配置 # 在 webpack.config.js 中有一个关于ManifestPlugin的配置,这里之前一直没管。每次启动项目时,就会卡在Starting the development server 的位置。既不报错,也启动不起来。后来查询了一下,才知道是ManifestPlugin这里配置错了。我这里呢,是直接删除了,确实没有用到。如果要用的话,应该怎么修改呢?entrypoints.main开始时,是一个数组,可以执行filter()方法。但修改上面的 webpack 配置后,entrypoints 不再一定包含 main 属性,如图。因此这里修改为:let list = []; // 获取到entrypoints所有的数组 for (const key in entrypoints) { list = list.concat(entrypoints[key]); } // 文件去重,并筛选掉.map结尾的文件 const entrypointFiles = [...new Set(list)].filter((fileName) => !fileName.endsWith('.map')); GitHub 上对应的改动 commit:https://github.com/wenzi0github/create-react-app-mpa/commit/16c6de467d8b18faa229b67855cf4014e7988c0a 2.4 配置命令行 # 所有执行的命令,执行文件均放在scripts目录中,然后在package.json中进行配置。 2.4.1 添加 commit 信息的检查 # 安装组件工具 commitlint 和 husky首先按照 commitlint 组件,我们在上面的 2.2.2 小节已经安装安装了 husky,若没有安装时,这里也要安装下 husky。#Install commitlint cli and conventional config npm install --save-dev @commitlint/{config-conventional,cli} #For Windows: npm install --save-dev @commitlint/config-conventional @commitlint/cli #Configure commitlint to use conventional config echo "module.exports = {extends: ['@commitlint/config-conventional']}" > commitlint.config.js 执行后,会在项目根目录生成 commitlint.config.js 的配置文件。然后将 commitlint 添加到 husky 的配置中:#Add hook npx husky add .husky/commit-msg 'npx --no-install commitlint --edit "$1"' 最后我们要添加上我们的规则,这里要修改commitlint.config.js文件了。首先所有的 commit 信息都要包含下面的关键词:module.exports = { extends: ['@commitlint/config-conventional'], rules: { 'type-enum': [ 2, 'always', [ 'feat', // 新功能(feature) 'fix', // 修补bug 'docs', // 文档(documentation) 'style', // 格式(不影响代码运行的变动) 'refactor', // 重构(即不是新增功能,也不是修改bug的代码变动) 'test', // 增加测试 'chore', // 构建过程或辅助工具的变动 'revert', // 回滚 ], ], }, }; 接着我们要添加自定义规则,约定每个 commit 信息都需要包含 tapid id:/* eslint-disable */ module.exports = { extends: ['@commitlint/config-conventional'], plugins: [ { rules: { 'need-tapd': (params) => { // http://km.oa.com/group/39598/articles/show/440469 // 提交规范的文档:https://iwiki.woa.com/pages/viewpage.action?pageId=172232748 const { subject, body, footer } = params; const reg = /(--bug=|--story=|--task=|--issue=)(\d+)(\s|$)/; if (reg.test(subject) || reg.test(body) || reg.test(footer)) { // 只要任意一个位置包含了tapd的相关信息即可 return [true]; } // 若没有关联tapd,则返回错误,并进行提示 return [ false, 'should contain --story or --bug or --task or --issue\n' + 'see https://iwiki.woa.com/pages/viewpage.action?pageId=172232748', ]; }, }, }, ], rules: { 'type-enum': [ 2, 'always', [ 'feat', // 新功能(feature) 'fix', // 修补bug 'docs', // 文档(documentation) 'style', // 格式(不影响代码运行的变动) 'refactor', // 重构(即不是新增功能,也不是修改bug的代码变动) 'test', // 增加测试 'chore', // 构建过程或辅助工具的变动 'revert', // 回滚 ], ], 'need-tapd': [2, 'always'], // 添加上我们的自定义规则 }, }; 若您还有其他的规则,则接着在 plugins.rules 中继续添加即可。 2.4.2 启动某个单独的项目 # 我们是一个多项目的应用,本地开发时,肯定是要启动一个或几个项目,而不能把所有的项目都启动起来。这里我们写了一个函数,通过传入的参数,来启动对应的项目。/* eslint-disable */ const chalk = require('react-dev-utils/chalk'); const glob = require('glob'); const path = require('path'); const getConfigByConfig = require('./getConfig'); const noFileMsg = (project) => `start error, the reason may be:\n 1. project enter error, please ensure you right; 2. in pages/${project} has not .tsx file, at least one .tsx fle\n`; // 在启动和构建之前执行的代码 // 主要用于检查输入的命令和读取配置文件 // feature-h5/card const beforeFn = async () => { const argv = process.argv.slice(2); // 获取传入的参数 const [project] = typeof argv === 'object' && argv.length ? argv : ['']; if (project.length) { const list = glob.sync(`src/pages/${project}/*.tsx`).filter((item) => !item.includes('test.ts')); // 获取该项目中所有的入口文件 if (list.length === 0) { // 若该项目没有入口文件,则进行提示 console.log(chalk.red(noFileMsg(project))); process.exit(1); } const ss = list .map((item) => { const { ext, name } = path.parse(item); if (ext === '.tsx') { const config = getConfigByConfig(`src/pages/${project}/${name}.json`); return { name, project, config, src: item, }; } return null; }) .filter(Boolean); return Promise.resolve(ss); } else { // 命令不正确 console.log(chalk.red(`please start like: \`npm run start home\`, or \`npm run start --page home\`\n`)); process.exit(1); } }; module.exports = beforeFn; 在scripts/start.js中,我们来调用这个 beforeFn 函数,获取要启动的项目:beforeFn() .then((projects) => { // project = [ // { // name: 'index', // project: 'home', // src: 'src/pages/home/index.tsx' // }, // { // name: 'share', // project: 'home', // src: 'src/pages/home/share.tsx' // } // ] startCheckBrowsers(projects); }) .catch((err) => { console.log(err); process.exit(1); }); 启动时,执行相应的命令即可,例如要启动 demo 项目时:$npm run start demo 2.4.3 如何只构建发生变动的项目 # 在流水线中构建时,如何只构建发生变动的项目?要完成这个功能,需要解决两个问题。 如何缓存已构建好的构建产物; 如何判断当前发生变动的项目; 先来说说如何缓存已构建好的构建产物。只构建发生变动的项目的前提,是可以缓存已构建项目的构建产物,然后可以将本次的构建产物与之前的构建产物进行合并。然后一并发到服务器上。无论您是将构建好的 html 发到 cdn 上,还是存储到数据库中,还是其他方式(比如我们内部的蓝盾流水线可以缓存构建产物),都是可以的。但若解决不了这个问题,则无法只构建当前发生变动的项目,因为下次的发布,会将之前已构建好的项目给顶掉。否则,您只能每次都得把所有的项目构建一遍。不过这种方式,一是没有必要,再有就是浪费时间。毕竟之前的项目没发生变动。第 2 个问题是如何判断当前发生变动的项目。我们公司是通过 Git 网站提供的接口,拿两个 commit id 获取到本地改动的文件,然后判断出哪个项目发生了变动,则构建该项目。若您没有这种条件,还有一种方式是,您当前分支的名称跟您要构建的项目相关,然后构建时,获取这个分支的名称。当然,您可以根据您公司现有的基础功能来决定采用什么方式。这里默认您已经有可以缓存构建产物的方式和了。完成获取所有发生变动项目的函数后,就可以在script/build.js中调用他了。try { const entries = await checkFn('npm run build'); let allResult = []; for (let key in entries) { const projects = entries[key].projects; // console.log(projects) if (projects.length) { const result = projects.map((item) => { return startCheckBrowser(item); }); allResult = allResult.concat(result); Promise.all(result).then(() => { console.log(`${projects[0].project} all builded`); console.log(`>> step 2: ${projects[0].project} has been builded`); }); } } await Promise.all(allResult); console.log(chalk.green(`>> step 3: all pages files builded success`)); console.log('>> step 4: starting upload static files'); // 构建完成后,则上传静态资源 uploadStatic(); console.log(`>> step 4.6: all files upload success`); } catch (err) { console.log(err); process.exit(1); } 3. 总结 # 在把create-react-app改造成多页面的应用过程中,最重要的是对 webpack 配置的改造。终于把教程写完了,在文章里可能有遗漏,或者不太详尽的地方,还请留言,一起探讨。
2024年10月20日
3 阅读
0 评论
0 点赞
2024-10-20
阿里这个不是瓜,是犯罪!
做为一个技术类的博客,本来不想评论一些实事。但这两天阿里发生的事情实在让人气愤。一个 P7 的主管在 7 月 27 日联合客户(济南华联超市),在酒桌上给自己的女下属灌酒,然后在酒店对其进行性侵。这位女下属在第二天醒来发现不对劲后,立刻报警,不过警察在 24 后就将其放了。女下属后连续想更高层的领导反映,但都被压了下来。最后迫不得已,只能在食堂以发传单、大喇叭的方式,来宣告自己的不公。涉事主管曲一(真名:王成文),长的一副斯文败类、人面禽兽的模样:阿里,你曾经直接开除那 4 位抢月饼的员工的魄力去哪儿了?合着你们就开除普通员工时雷厉风行,但涉及到主管时就开始磨磨唧唧了。一会儿说在调查,一会儿说怕影响你的名声。我特么连脸都不要了,你告诉我怕影响我的名声?你怕影响谁的名声啊。判不判刑,判多少年,判不判死刑,是法院的事儿。但你阿里包庇员工,欺上瞒下,就是你阿里的不对了。从阿里的董事局主席兼 CEO 张勇,后知后觉地在今天(8 月 8 日)凌晨在内网发帖,“震惊、气愤、羞愧”?你早干嘛去了,都已经 10 多天多去了。现在才震惊、气愤、羞愧吗?震惊的是内部没压住这件事儿吧。阿里有对微博的入股,在对阿里负面事件的控评、删帖、撤热搜,有一把好手。无论网友怎么顶一个事件,就是上不了热搜。在之前阿里的“蒋凡事件”,控评、删帖到丧心病狂,甚至到了被网信办警告罚款的程度。而这次的微博热搜也一样,热搜榜上仅仅是“阿里巴巴”4 个字,连什么事件都没露出来。目前最新的进展是:同城零售事业群总裁李永和和 HRG 徐昆引咎辞职,阿里巴巴首席人力资源官童文红记过处分,涉嫌男员工王成文被辞退,永不录用,其是否存在违法行为,警方正在调查取证。
2024年10月20日
4 阅读
0 评论
0 点赞
2024-10-20
NextJs 如何按服务端或浏览器端的类型分别打包
NextJs 是一款优秀的 react 同构直出框架,写一次代码,能够同时在服务端和浏览器端。这是因为 NextJs 会在服务端和浏览器分别打包一份,然后通过数据进行互通。可是若一些模块只能在浏览器端使用,或只能在服务端使用,该怎么办呢? 1. 只在浏览器端使用的模块 # 例如@fingerprintjs/fingerprintjs组件,在 3.0.3 版本及之前,若在 NextJs 中引用时,会直接报错。这是我之前提的 issue:https://github.com/fingerprintjs/fingerprintjs/issues/602。例如:import FingerprintJS from '@fingerprintjs/fingerprintjs'; useEffect(() => { FingerprintJS.load(); }, []); 会提示 window 变量不存在的错误: ReferenceError: window is not defined 这是因为,在 FingerprintJS 模块中,直接调用了 window 变量,而 window 变量只有在浏览器端才有。因此,引用模块后,啥也没干,就已经报错了。解决方案,就是使用 import 异步引入:useEffect(() => { // 这里是浏览器的环境 import('@fingerprintjs/fingerprintjs') .then((FingerprintJS) => FingerprintJS.load()) .then((fp) => fp.get()) .then((result) => console.log(result.visitorId)); }, []); 这里也仅仅是用 FingerprintJS 模块来举个例子,不过该模块已经从 3.0.6 版本开始解决这个问题了。它会在调用 load 方法时,才会去引用 window 变量。 2. 只在服务端使用的模块 # 某组件 A 引用了 net 模块、stream 模块等只有在服务端才有的模块,这些模块就只能在服务端使用。若直接 import 的话,NextJs 也会在浏览器端打包一份,但这个组件在浏览器端又无法使用。我们公司一个很有名的名字服务-北极星模块,一个只在服务端使用的模块。开发的过程中,是没有感觉的,但打包时就会提示找不到模块:有两种解决方案。 2.1 只在 server 端引用 # NextJs 可以自定义 sever 文件,自定义 server 的文档:https://nextjs.org/docs/advanced-features/custom-server。如果可以的话,我们把代码逻辑引导 sever 文件中。// server.js const { createServer } = require('http'); const { parse } = require('url'); const next = require('next'); const Polaris = require('@tencent/polaris'); // 服务模块在这里引用,就可以了 const dev = process.env.NODE_ENV !== 'production'; const app = next({ dev }); const handle = app.getRequestHandler(); app.prepare().then(() => { createServer((req, res) => { // Be sure to pass `true` as the second argument to `url.parse`. // This tells it to parse the query portion of the URL. const parsedUrl = parse(req.url, true); const { pathname, query } = parsedUrl; if (pathname === '/a') { console.log(Polaris); app.render(req, res, '/a', query); } else if (pathname === '/b') { app.render(req, res, '/b', query); } else { handle(req, res, parsedUrl); } }).listen(3000, (err) => { if (err) throw err; console.log('> Ready on http://localhost:3000'); }); }); server.js 本身就是服务端的文件,它不会通过 NextJs 的打包系统引入到浏览器端。 2.2 在服务侧按需引入 # 若不方便将代码执行路径引导到服务端的文件。我们可以在服务侧按需引入,然后在 next.config.js 中配置不要打包到浏览器端。export default function App { }; export async function getStaticProps() { const Polaris = require('@tencent/polaris'); console.log(Polaris); return {}; } getStaticProps()方法在 NextJs 中,就是服务端运行的方法。我们在这里面使用require()来按需引入。接着在 next.config.js 中:const withAntdLess = require('next-plugin-antd-less'); module.exports = (phase) => withAntdLess({ webpack: (cfg, { isServer, webpack }) => { const config = cfg; if (!isServer) { // 在浏览器端,忽略这些模块的打包 const ignoreList = ['@tencent\\/polaris', 'dns', 'dotenv']; ignoreList.forEach((n) => { config.plugins.push(new webpack.IgnorePlugin({ resourceRegExp: new RegExp(n) })); }); } return config; }, }); 在浏览器端,忽略这些模块的打包。 3. 总结 # NextJs 是一个在服务端和浏览器都可以运行的 react 框架,我们在使用的使用要特别注意有哪些是只能在客户端使用,哪些是只能在服务端使用。
2024年10月20日
5 阅读
0 评论
0 点赞
2024-10-20
leetcode1337 矩阵中战斗力最弱的 K 行的一种新颖解法
这是一道难度为“简单”的题目,我们先来看下题干https://leetcode-cn.com/problems/the-k-weakest-rows-in-a-matrix:给你一个大小为?m?* n?的矩阵?mat,矩阵由若干军人和平民组成,分别用 1 和 0 表示。 请你返回矩阵中战斗力最弱的?k?行的索引,按从最弱到最强排序。 如果第?i?行的军人数量少于第?j?行,或者两行军人数量相同但 i 小于 j,那么我们认为第 i 行的战斗力比第 j 行弱。 军人 总是 排在一行中的靠前位置,也就是说 1 总是出现在 0 之前。 这里有个矩阵:const mat = [ [1, 1, 0, 0, 0], [1, 1, 1, 1, 0], [1, 0, 0, 0, 0], [1, 1, 0, 0, 0], [1, 1, 1, 1, 1], ]; 因为军人总是在最前面,很多人最直观的算法就是,按照列,从上往下找,碰到第一个 0 的那一行就是最小的,遍历个遍,就能把找出最弱的前 K 行了。 1. 一个新颖的思路 # 但这里我提供了另一种思路:通过一个公式,计算出每行的战斗值: 该行的军人数量 * 100 + 行号。 按照这个公式,我们来实现一个算法: 每行:该行的军人数量 * 100 + 行号,得到该行的战斗值; 然后按照算出的战斗值进行从小到达的排序; 取前 k 个数据,然后进行取余,即得到结果; 我们用 C 语言来实现一下:// 升序 int cmp(const void *a, const void *b){ return *(int *) a - *(int *) b; } int *kWeakestRows(int **mat, int matSize, int *matColSize, int k, int *returnSize){ static int result[110]; int i, j, num, weighting; weighting = 100; for (i = 0; i < matSize; i++) { num = 0; for (j = 0; j < *matColSize; j++) { if (mat[i][j] == 0) { break; } num += mat[i][j]; } result[i] = num * weighting + i; // 军人数量 * 100 + 行号 } qsort(result, matSize, sizeof(result[0]), cmp); // 排序 *returnSize = k; for (i = 0; i < k; i++) { result[i] = result[i] % weighting; // 取余 } return result; } 这思路是不是很新颖?可是为什么这么计算,也是正确的呢? 2. 思路的剖析 # 这里我们要注意题目中给出的两个判断条件: 优先判断军人数量,军人数量少的则战斗力弱; 军人数量相同的,则行号小的战斗力弱; 而最后又需要的是最弱的行号。因此,我们把军人数量按照一定的权重进行放大后,其实大小关系是没有的,该相等的还是相等,该小的还是小。最后再加上行号,则可以把军人数量和行号两个维度结合算出一个战斗值来。这个算出来的战斗值就会满足上面的两个判断条件。 2.1 权重值的选择 # 为什么权重是 100 呢?我们先说下为什么不能小于 100 。假如我们设定权重是 90,上面的算法就会存在一个漏洞:行号的加持超过军人数量。举个例子,比如第 0 行有 1 个军人,第 99 行有 0 个军人;按照上面的算法,得到第 0 行的战斗值是 90(军人数量 1 * 90 + 行号 0),第 99 行的战斗值是 99(军人数量 0 * 99 + 行号 99),则第 0 行小于第 99 行;但实际上,是第 0 行的战斗力更强(因为军人数量更多)。题目要求整个战斗矩阵最大只有 100 行,当我们把权重设置成 100 时,则行号再大,也追不上军人数量的加持。实际上,权重 weighting >= 100 都是没问题的,只不过我们这里取了一个最小的临界值而已。 2.2 最后的那一步取余 # 取余这个操作,就是为了得到我们的行号。我们选择加持的权重的值大于最大行的行号(从 0 开始计数),对权重再进行取余后,得到的一定是行号。听懂掌声!
2024年10月20日
4 阅读
0 评论
0 点赞
1
...
46
47
48
...
213