基于『css in js 』理念,Webpack 默认会把样式文件打包到 js 文件中进行异步加载。这样做的好处是节省了请求数,毕竟把多个文件压缩成了一个,只需要一次网络请求,但是这样做也会引入副作用:
所以针对第二种场景,我们需要进行规范化的拆包约定,把 JS Bundle 里不经常变化的部分样式(如 reset.css 等)提出来,生成独立与业务逻辑的 CSS 文件单独存储,同时也要把经常随业务变化的样式直接打入 JS Bundle 中,尽可能的利用 『css in js』的优势。
脚手架工程目录,我们只关注样式相关内容,可以简化如下:
├── src
│ ├── pages // 页面入口
│ │ └── pageA // 页面入口 A
│ │ ├── index.html // 页面入口模板
│ │ ├── index.js // 页面入口 JS 逻辑
│ │ └── index.scss // 页面业务样式,会经常发生变化
│ └── common
│ └── styles // 全局公共样式,通常很久才会变动一次
│ └── base.scss
目录拆包约定:
所以一个典型的构建产物目录结构应该是如下的样子
resource
├ common
│ └── base.xxx.css // 全局样式
├ pages // 页面入口
│ └── pageA // 页面入口 A
│ └── index.xxx.js // 页面入口 index Bundle ,内含 JS 逻辑和业务样式(css in js)
内部工程化方案引入 mini-css-extract-plugin
插件来做 CSS 样式提取,使用官方标准的配置方法来实现上述拆包约定:
首先配置 mini-css-extract-plugin ,指定提取 common/styles 目录下的样式为独立的 css 文件。
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
module: {
rules: [
{
// 提取 common/styles 中样式为单独的 css 文件
test: /\/common\/styles\/.*(css|scss|sass|less)$/ i,
use: [MiniCssExtractPlugin.loader, 'css-loader'],
},
],
},
plugins: [new MiniCssExtractPlugin({
// 指定 base.css 生成到 common 目录中
chunkFilename: 'common/[name].[contenthash].css',
})]
};
mini-css-extract-plugin 对样式的提取是以页面入口为单位。如上有关 mini-css-extract-plugin 的配置,虽然能够完成对 common/styles 目录中样式的提取,但是它会为每个页面入口分别都生成对应的 css 文件,而没有能力生成全局唯一的 css 文件。我们额外引入 Webpack splitChunks ,基于它提取公共 chunk 的能力,可以很方便的实现我们的目标:
optimization: {
splitChunks: {
cacheGroups: {
base: {
name: "base",
// 对 common/styles 中的样式进行提取,生成公共 chunk
test: /\/common\/styles\/.*(css|scss|sass|less)$/,
chunks: "all",
enforce: true
}
}
}
}
于是,在构建过程中,我们通过提取 common/styles
中样式,成功生成了全局唯一的公共样式文件 base.50289158.css
。整个构建产物目录如下图:
后续对产物进行说明时,方便起见,均省略其文件名的 contenthash 值
观察仔细的同学,可以从上图的构建结果中发现,生成 base.css 的同时,在它的同级目录下也生成了预期外的同名 js 文件 base.js (上图中因 base.css 和 base.js 文件内容不同而导致 contenthash 不同,但是两者同源)。
打开这个 base.js ,我们可以看到如下代码:
(window["WebpackJsonp"] = window["WebpackJsonp"] || []).push([["base"],{
/***/ "./src/common/styles/base.scss":
/*!*************************************!*\
!*** ./src/common/styles/base.scss ***!
\*************************************/
/*! no static exports found */
/***/ (function(module, exports, __Webpack_require__) {
// extracted by mini-css-extract-plugin
/***/ })
}]);
//## sourceMappingURL=base.af983635.js.map
base.js
中声明了一个 base chunk
,内部只包含一个对 base.scss 的 JS 空模块定义。这个定义是没有实际意义的,只会导致我们的构建中额外生成了一个没有用的 js 文件,页面执行时对它的加载就会导致多一次网络请求,显然这是不符合预期的。
经过调研,发现这个诡异的现象是 Webpack 4 中设计 chunk 解析流程时的历史遗留问题(是一个设计缺陷,也可以认为是一个bug)。
Webpack 4 要求所有的 module (不管是图片还是样式) 在构建时,经过 loader 加载后都必须返回一个 js 模块,所以我们在加载 base.scss 时也会生成对应的 js module (即 base.js 中定义的空模块),这个机制通常情况下不会发生问题,因为所有被引入的资源模块,总能找到它的入口父模块, Webpack 通常会把这个生成的空模块打入它所对应的入口父模块的 chunk 中,所以不会产生额外的文件。
但是当我们使用 splitchunk 配合 mini-css-extract-plugin 提取单独的公共样式文件(base.css)时,这个样式文件(base.scss)在生成对应的 js 模块后,找不到对应的入口父模块(因为当前 chunk 是通过 splitchunks 产生,本身就是一个独立的 chunk,没有上文所述可以归属的入口父模块),Webpack 只能为这个独立的 css chunk 单独生成一个额外的 js 文件。
这个问题在 Webpack v5.0.0-beta.17 已经得到修复,Webpack 5 会判断当 splitchunks 生成的独立 chunk 中若不包含 js 模块,则既不会为该 chunk 生成独立的 js 文件,也不会把它加入到页面依赖中。相关修复的 COMMIT可以参考这次提交。但是遗憾的是 Webpack 4 目前并不打算修复这个问题。
考虑到相当一部分项目因历史原因无法升级到 Webpack 5,我们还需要回到 Webpack 4 中寻求解决方案。
假设我们把产物这个多余的文件 (base.js) 手动删除(因为看起来这个文件中的定义模块没有任何功能逻辑,感性上对页面功能不应该产生影响),那么当页面运行时,会发现页面出现白屏了,页面入口的代码压根就没有被执行。
这是因为 Webpack 的页面启动模块,会把 base.js 对应的 chunk 视为必要依赖,若该 chunk 未加载,则后续业务逻辑不会被执行。我们打开页面启动(bootstrap)模块源码可以得到验证:
/******/ (function(modules) { // WebpackBootstrap
// 中间代码省略
/******/ function checkDeferredModules() {
/******/ var result;
/******/ for(var i = 0; i < deferredModules.length; i++) {
// 这里 deferredModule 为入口所依赖的所有 chunks 声明,由下面的 deferredModules.push([0,"vendor","base"]); 决定。只有全部 chunks 都加载完毕,才能触发页面入口的业务逻辑。
/******/ var deferredModule = deferredModules[i];
/******/ var fulfilled = true;
// 确认所有依赖模块都已经加载完毕,则 fulfilled 保持为 true
/******/ for(var j = 1; j < deferredModule.length; j++) {
/******/ var depId = deferredModule[j];
/******/ if(installedChunks[depId] !== 0) fulfilled = false;
/******/ }
/******/ if(fulfilled) {
/******/ deferredModules.splice(i--, 1);
// 触发入口业务逻辑
/******/ result = __Webpack_require__(__Webpack_require__.s = deferredModule[0]);
/******/ }
/******/ }
/******/ return result;
/******/ }
// 中间代码省略
// 获取所有挂载到 window.WebpackJsonp 的 chunk
/******/ var jsonpArray = window["WebpackJsonp"] = window["WebpackJsonp"] || [];
// 加载挂载到 window.WebpackJsonp 的 chunks,加载完成会标识 installedChunks 为 true
/******/ for(var i = 0; i < jsonpArray.length; i++) WebpackJsonpCallback(jsonpArray[i]);
/******/ var parentJsonpFunction = oldJsonpFunction;
/******/ // 这里声明了页面依赖的 chunks 有哪些,只有依赖都加载完毕才能执行页面后续逻辑
// 其中 base 对应了我们通过 splitchunks 抽取出来的 css chunk 即 base.js
// 0 对应页面入口 index.js,
// vendor 对应公共 JS vendor.js
/******/ deferredModules.push([0,"vendor","base"]);
/******/ // 这个方法会确认所有依赖模块都已经加载完毕,然后触发入口业务逻辑
/******/ return checkDeferredModules();
})({/* 这里传入页了面入口 index.js 和它的直接依赖模块 */ })
上面模板代码中最关键的部分为deferredModules.push([0,"vendor","base"]);
其中 deferredModules 即用来声明页面依赖的 chunk:
声明中第三个 chunk base 是我们需要处理的对象,我们需要把这个依赖在依赖声明数组中去掉。我们需要找到这段页面入口的模板代码是如何生成的。
经过调研,发现上面给出的 Webpack 启动(bootstrap) 模块代码由 Webpack 的 lib/web/JsonpMainTemplatePlugin 生成,启动模块中关键的依赖声明部分deferredModules.push([0,"vendor","base"]);
的生成逻辑如下:
mainTemplate.hooks.startup.tap(
"JsonpMainTemplatePlugin",
(source, chunk, hash) => {
if (needEntryDeferringCode(chunk)) {
if (chunk.hasEntryModule()) {
const entries = [chunk.entryModule].filter(Boolean).map(m =>
[m.id].concat(
Array.from(chunk.groupsIterable)[0]
.chunks.filter(c => c !== chunk)
.map(c => c.id)
)
);
return Template.asString([
"// add entry module to deferred list",
`deferredModules.push(${entries
.map(e => JSON.stringify(e))
.join(", ")});`,
"// run deferred modules when ready",
"return checkDeferredModules();"
]);
} else {
return Template.asString([
"// run deferred modules from other chunks",
"checkDeferredModules();"
]);
}
}
return source;
}
);
可以看到,页面入口依赖是由 chunk.groupsIterable 的内部依赖决定。
通过上图中的调试情况,我们可以验证,入口对应 chunkGroup 中的确存在对 base chunk
的依赖。
我们需要做的事情则转换为:如何避免 base chunk
进入入口 chunkGroup 的依赖中。
如上,当前状态,我们产出了三个 bundle 产物。
其中 base chunk
所对应的文件即为不应该出现的 Bundle 产物 (base.js),我们需要把它从依赖图中去掉。
通过调试,可以看到该 base chunk 内部结构如下:
上图中可知,base 包含两种子模块。我们需要对他们进行分别处理:
NormalModule 对象的 _source 属性用来保存该模块的 JS 代码,查看可以看到
该 NormalModule JS 内容为// extracted by mini-css-extract-plugin
与上文构建产物中 base.js 文件中空模块的代码一致。我们希望可以把这段注释的内容作为特征判断的依据,来区分当前 NormalModule 是否因 Css 文件提取而产生。若它是因 Css 提取而产生,我们需要阻止它单独生成 chunk 文件(即阻止它生成 base.js)。
为了验证这段注释代码确实可以稳定可靠的帮助我们判断的 NormalModule 类型,我们在 mini-css-extract-plugin 的对应 loader 中找到它的生成过程如下:
const pluginName = 'mini-css-extract-plugin';
let resultSource = `// extracted by ${pluginName}`;
resultSource += options.hmr
? hotLoader(result, { context: this.context, options, locals })
: result;
return callback(null, resultSource);
通过上述代码,我们可以确认这段注释内容是稳定可控的。我们后续可以通过判断 Module 的内容(_source 属性)是否包含 extracted by mini-css-extract-plugin 关键字,来确认当前的 Module 是否为因提取样式而导致的副作用产物(空模块)。
由于需要保持 Webpack 模块依赖图的完整性,我们不能简单的把这个 JS 副作用产物空模块从依赖图中直接删掉(删掉会导致依赖图不完整,运行页面时会造成模块加载错误),而只能采取折中的办法,把这个模块的内容插入到入口 js 文件(index.js)中,避免单独的 base.js 文件产出。
为了实现这个目标,我们在构建阶段需要做的事情如下:
// extracted by mini-css-extract-plugin
的『NormalModule』 类型模块,如果满足上述条件,则说明该 chunk 中实际上不含任何 JS 逻辑,为我们需要进行特殊处理(最终删除)的『问题 chunk』(上述 base chunk
即为这种类型的 chunk)为了实现上述目标,我们需要编写自定义 Webpack 插件。
Webpack 中 compilation 对象负责推进具体的构建流程,compilation 对象上存在以下需要我们关注的生命周期:
我们要选择合适的 Webpack 生命周期(hooks)来注入我们的处理逻辑。
我们的 Webpack plugin 要做的事情有两部分:
2 . 需要阻止『问题 chunk』对空模块 js 文件 (base.js) 的生成。 第一步,对依赖图的修改要在 seal 之后执行,否则我们无法拿到所有的 module 模块;对依赖图的修改也要在 chunk 生成 chunk asset 之前执行,否则我们的修改无法及时影响到文件输出过程。由此可以确认对依赖图的修改操作应该在 beforeChunkAssets 阶段 (该阶段在 seal 之后,chunkAssets 之前)执行。
第二步,阻止『问题 chunk』生成空模块文件(base.js)的操作,应该在 chunk assets 生成后进行,如果我们提前阻止了它的生成,那么同时会对 css 文件(base.css) 的生成产生干扰。所以第二步操作应该在additionalChunkAssets 阶段执行
核心处理代码如下
const CSS_MODULE_TYPE = 'css/mini-extract';
// 在 beforeChunkAssets 阶段注入处理逻辑
compilation.hooks.beforeChunkAssets.tap(pluginName, () => {
// 找到所有由 splitchunks 优化导致产生的 chunk
const splitChunks = compilation.chunks.filter(
thisChunk => thisChunk.chunkReason && thisChunk.chunkReason.includes('split chunk')
);
splitChunks.forEach(splitChunk => {
// 存储空 JS module,对应上述 base.js
const uselessModules = [];
// 存储不是 CssModule 的模块,可能是任意类型 Module
const noCssModules = [];
// 存储 Css Module
const cssModules = [];
Array.from(splitChunk.modulesIterable).forEach(mod => {
if (mod.type !== CSS_MODULE_TYPE) {
noCssModules.push(mod);
} else {
cssModules.push(mod);
}
});
noCssModules.forEach(nonCssMod => {
if (nonCssMod._source && nonCssMod._source._value === '// extracted by mini-css-extract-plugin') {
uselessModules.push(nonCssMod);
}
});
// 处理 『问题 chunk』
if (uselessModules.length === noCssModules.length) {
// 把『问题 chunk』中所有 JS 空 module 移动到 『源 chunk』,即上述 base chunk 中 NormalModule 空模块移动到 index chunk 中,从而把 base.js 的内容合并到 index.js 中
uselessModules.forEach(uselessModule => {
uselessModule.reasons.forEach(reason => {
reason.module.chunksIterable.forEach(previouslyConnectedChunk => {
splitChunk.moveModule(uselessModule, previouslyConnectedChunk);
});
});
});
splitChunk.groupsIterable.forEach(group => {
// 把『问题 chunk』从入口 chunk group 中删掉
group.removeChunk(splitChunk);
});
}
});
});
compilation.hooks.additionalChunkAssets.tap(pluginName, chunks => {
chunks.forEach(chunk => {
// problemChunkInfos 存储了所有的『问题 chunk』
if (this.problemChunkInfos[chunk.id]) {
chunk.files.forEach(file => {
if (path.extname(file) === '.js') {
// 组织生成空 JS 文件,对应上述 base.js
chunk.files.pop();
delete compilation.assets[file];
}
});
}
});
});
});
由此,我们初步完成 plugin 的开发,把它引入项目:
const Plugin = require('./Webpack-plugin/mini-css-extract-useless-js-plugin');
// ... 省略
WebpackOptions.plugins.push(new Plugin());
执行构建,查看 common 目录构建产物:
可以发现,common 下面已经不会出现 base.js,我们已经成功的阻止了它的生成。
然后打开页面入口 index.js 中查看,可以看到之前 base.js 中的空模块内容确实已经合并到 index.js 中了。
但是当我们打开页面进行调试时,却发现,页面中并没有加载 base.css
中的样式。由此推测,虽然目前生成了 base.css 文件,但是页面 html 文件中并没有对它的引用。
我们查看页面模板 HTML 代码,发现其中并没有包含对 base.css 的引用 link 标签。于是验证了这个结论。
<!doctype html><html lang="zh-cn"><head><meta charset="utf-8"><title></title><link rel="icon" href="//s3a.pstatp.com/toutiao/resource/ntoutiao_web/static/image/favicon_5995b44.ico" type="image/x-icon"><link rel="dns-prefetch" href="//s3.bytecdn.cn/"><meta name="screen-orientation" content="portrait"><meta name="x5-orientation" content="portrait"><meta name="format-detection" content="telephone=no"><meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no,minimum-scale=1,maximum-scale=1,minimal-ui,viewport-fit=cover"><meta name="apple-mobile-web-app-capable" content="yes"></head><body><div id="root"></div><script type="text/javascript" src="//s3.pstatp.com/ies/aatestcss/dll/index.0e499cba.js"></script><script type="text/javascript" src="//s3.pstatp.com/ies/aatestcss/common/vendor.4ed04928.js"></script><script type="text/javascript" src="//s3.pstatp.com/ies/aatestcss/pages/home/index.dd8ee276.js"></script></body></html>
进行到这里,我们需要确认原来 HTML 模板中对 base.css 的依赖,是如何丢失的。
我们再重新梳理下之前处理『问题 chunkl』的思路
可以看到上述步骤中,我们在处理 base.js 相关逻辑后,直接删掉了入口对 base chunk
的依赖。但是实际上 base.css 文件的相关引用信息依然保存在 base chunk 中。
我们的页面入口此时与 base chunk 已经没有任何关系,所以虽然 base.css 依旧跟随 base chunk 生成了,但是入口拿不到任何 base.css 相关的引用信息,也就无法加载这个文件。
由此,我们尝试在对 base.js 的处理逻辑后,追加对 base.css 的相关处理,在处理依赖图时,把记录有 css 样式代码的 CssModule 也拿到 index chunk 中
compilation.hooks.beforeChunkAssets.tap(pluginName, () => {
// 代码省略 ...
// 把『问题 chunk』中所有 CSS module 移动到 『源 chunk』,即把上述 base.css 移动到 index chunk 中。因为这里如果不处理,则 html-Webpack-plugin 处理 HTML 模板时,无法写入对 base.css 的引用。
cssModules.forEach(cssModule => {
cssModule.reasons.forEach(cssModuleReason => {
cssModuleReason.module.reasons.forEach(uselessModuleReason => {
uselessModuleReason.module.chunksIterable.forEach(originChunk => {
splitChunk.moveModule(cssModule, originChunk);
});
});
});
});
});
重新构建后,我们刷新页面,发现页面样式终于恢复了。
但是当我们回头查看构建产物,发现 base.css 消失了,同时页面入口目录 page/home 中多出了一个样式文件 index.css 。
我们点开 page/home/index.css 文件查看内容
发现本应该出现在 base.css 中的内容,出现在了 index.css 中。即 base.css 的文件名由 base.css 变成了 index.css,生成路径从 common 下移动到了页面入口所在目录,这样又回退到了未开启 splitchunk 的状态,显然是不满足我们的需求的。
此时我们需要查清楚,这个诡异的 index.css 到底是怎么产生的。
我们都知道,独立的 css 文件必定是由 mini-css-extract-plugin
提取产生,查看它的源码,我们可以看到 css 文件的生成逻辑:
compilation.chunkTemplate.hooks.renderManifest.tap(pluginName, (result, { chunk }) => {
const renderedModules = Array.from(chunk.modulesIterable).filter(module => module.type === MODULE_TYPE);
if (renderedModules.length > 0) {
result.push({
render: () => this.renderContentAsset(compilation, chunk, renderedModules, compilation.runtimeTemplate.requestShortener),
filenameTemplate: this.options.filename,
// path options 决定 css 文件名与输出目录的生成规则。这里由传入的 chunk 决定
pathOptions: {
chunk,
contentHashType: MODULE_TYPE
},
identifier: `${pluginName}.${chunk.id}`,
hash: chunk.contentHash[MODULE_TYPE]
});
}
});
mini-css-extract-plugin 针对 CssModule 的 CSS 文件生成过程,向 Webpack compilation 对象注册了 manifest 声明(manifest 记录了一个 chunk 该怎么生成 chunk assets,即如何生成 css 文件)。
Webpack 中 compilation 会接收这个 manifest 声明,进行 CSS 文件输出。
我们查看 Webpack 中 compilation 的源码,找到 createChunkAssets 方法,可以看到产物路径(包含文件名)生成的语句:
file = this.getPath(filenameTemplate, fileManifest.pathOptions);
通过上面的源码分析,我们可以得出结论,Css 文件在生成时,其文件名和路径均由当前所在 Chunk 的 id 和路径决定,而与模块本身的 id 无关。
由于上面我们的 Webpack plugin 逻辑中,在 base.css 文件生成之前,会把对应的记录有 CSS 样式代码的 CssModule 从 base chunk
移动到了 index chunk,所以 CssModule 在生成对应 CSS 文件时,会基于 index chunk
的上下文进行(而不是根据 base chunk
),所以不管是文件名,还是输出路径,都与 index chunk 对齐。与上述产物特征表现保持一致。
而此时我们所需要的 css 的文件名和路径却都保存在 base chunk 中。
所以,我们需要让 css 文件的生成过程在 base chunk 中进行,然后在该 css 文件实际生成后(生成 bse.css后),再把生成的文件从 base chunk 中添加到 index chunk,以保证该文件的名称和路径符合我们的需要。
由上面分析,我们需要把之前对 CssModule 相关处理逻辑,从 beforeChunkAssets 阶段移动到 additionalChunkAssets 阶段,变更代码如下:
compilation.hooks.beforeChunkAssets.tap(pluginName, () => {
// 代码省略 。。。
// 代码块删除开始
// 把『问题 chunk』中所有 CSS module 移动到 『源 chunk』,
cssModules.forEach(cssModule => {
cssModule.reasons.forEach(cssModuleReason => {
cssModuleReason.module.reasons.forEach(uselessModuleReason => {
uselessModuleReason.module.chunksIterable.forEach(originChunk => {
splitChunk.moveModule(cssModule, originChunk);
});
});
});
});
// 代码块删除结束
});
compilation.hooks.additionalChunkAssets.tap(pluginName, chunks => {
chunks.forEach(chunk => {
// problemChunkInfos 存储了所有的『问题 chunk』
if (this.problemChunkInfos[chunk.id]) {
chunk.files.forEach(file => {
if (path.extname(file) === '.css') {
this.problemChunkInfos[chunk.id].originChunks.forEach(originChunk => {
// 把已经生成的 css 文件注入到入口模块中,html-Webpack-plugin 在生成 html 时会去解析这里的依赖,从而自动生成对 css 文件的 link 引用标签。
originChunk.files.push(file);
});
} else if (path.extname(file) === '.js') {
// 组织生成空 JS 文件,对应上述 base.js
chunk.files.pop();
delete compilation.assets[file];
}
});
}
});
});
});
完成插件的最后修改后,我们进行构建,查看产物状态:
看到此时产物的目录结构已经符合预期,然后我们继续查看生成的 HTML 模板文件:
<!doctype html><html lang="zh-cn"><head><meta charset="utf-8"><title></title><link rel="icon" href="//s3a.pstatp.com/toutiao/resource/ntoutiao_web/static/image/favicon_5995b44.ico" type="image/x-icon"><link rel="dns-prefetch" href="//s3.bytecdn.cn/"><meta name="screen-orientation" content="portrait"><meta name="x5-orientation" content="portrait"><meta name="format-detection" content="telephone=no"><meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no,minimum-scale=1,maximum-scale=1,minimal-ui,viewport-fit=cover"><meta name="apple-mobile-web-app-capable" content="yes"><link href="//s3.pstatp.com/ies/aatestcss/common/base.50289158.css" rel="stylesheet"></head><body><div id="root"></div><script type="text/javascript" src="//s3.pstatp.com/ies/aatestcss/dll/index.0e499cba.js"></script><script type="text/javascript" src="//s3.pstatp.com/ies/aatestcss/common/vendor.4ed04928.js"></script><script type="text/javascript" src="//s3.pstatp.com/ies/aatestcss/pages/home/index.dd8ee276.js"></script></body></html>
可以看到 HTML 模板中已经成功生成了对 base.css 文件的引用。
然后我们回到本文开头提到的入口启动模块,确认启动模块的依赖状态。查看 index.js 代码:
/******/ var jsonpArray = window["WebpackJsonp"] = window["WebpackJsonp"] || [];
/******/ for(var i = 0; i < jsonpArray.length; i++) WebpackJsonpCallback(jsonpArray[i]);/******/
/******/
/******/ // 可以看到,这里已经没有了 base chunk 的声明
/******/ deferredModules.push([0,"vendor"]);
/******/ // run deferred modules when ready
/******/ return checkDeferredModules();
可以看到,当前页面中只存在入口逻辑和 vendor 的 chunk 依赖,base chunk 已经被成功删除。由此可以确认,问题得到完美解决。
本文由哈喽比特于2年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/dQU1zBnNgYk2oCC2kDXCcg
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。