Material Components for the Web(MDC Web)快速上手:从 CDN 到 npm + Webpack 的完整实践指南
【免费下载链接】material-components-webModular and customizable Material Design UI components for the web项目地址: https://gitcode.com/gh_mirrors/ma/material-components-web
Material Components for the Web(MDC Web)是 Google 核心工程师与 UX 设计师团队开发的 Material Design 官方 Web 组件库,旨在帮助开发者以可靠的开发流程构建美观、功能完整的 Web 项目。本文以仓库根目录 README.md 为主线,系统讲解 MDC Web 的模块化架构、两种快速上手路径(CDN 与 npm),并深入 Webpack + Sass + ES2015 的完整构建配置、多种 JavaScript 导入方式与组件实例化技巧,让你既能"5 分钟跑起来",也能在生产环境中按需定制。
MDC Web 是什么:模块化、可定制的 Material Design Web 组件库
MDC Web 是 Material Design Lite 的说明可以看到,它被特意设计为可适配多种主流 Web 框架的架构(详见 docs/framework-wrappers.md)。
MDC Web 致力于无缝融入更广泛的适用场景:从简单的静态网站,到复杂的 JavaScript 重度应用,再到混合的客户端/服务端渲染系统。无论你是否已经深度使用某个框架,都能以轻量、惯用的方式将 Material Components 集成到你的站点中。
适用场景一览
- 静态站点 / 原型:使用 CDN 引入预编译产物即可;
- 工程化项目:通过 npm 安装并按需引入组件包,配合 Sass 与 ES2015 模块参与构建;
- 框架集成:底层采用 Foundation / Adapter 分层,为 React、Angular 等框架封装提供了接口基础。
维护状态提示(重要):根据仓库 README 的声明,该项目已不再积极维护——虽然自动化更新可能仍会发生,但团队不再优先规划新特性或缺陷修复,且这些自动更新未来也会被关闭。在选择新项目技术栈时请结合这一现状评估;本文面向的是希望使用或理解 MDC Web 既有能力的开发者。
版本与发布节奏(来自 README 的官方说明)
- 遵循 semver 语义化版本控制,你可以控制何时引入破坏性变更;
- 典型的发布节奏为2 周一次:每月包含一次带破坏性变更的 major 版本,中间穿插带缺陷修复的 patch 版本;
- 当前仓库中 packages/material-components-web/package.json 记录的版本为14.0.0。
核心架构:包划分与 Foundation / Adapter 模式
要理解 MDC Web 的"模块化",首先要看它的包结构。根据 docs/code/architecture.md,MDC Web 被拆分为多个包(package),每个包要么是子系统(Subsystem),要么是组件(Component):
- 子系统:被众多组件复用,通常描述样式(如颜色、主题)或动效(如动画)。例如
mdc-animation、mdc-theme、mdc-ripple、mdc-elevation、mdc-shape、mdc-typography等; - 组件:如
mdc-button、mdc-textfield、mdc-dialog,组件包往往依赖多个子系统包,但组件之间很少互相依赖; - 每个组件都可独立于其他组件单独使用,这是模块化设计的基本原则。
仓库根目录下packages/目录(查看全部组件包)正是这一设计的落地:每个包自带 README、Sass 源文件、TypeScript 源文件与 package.json。
三层技术栈:Sass / HTML / JavaScript
| 层 | MDC Web 的做法 |
|---|---|
| Sass | 所有 CSS 均由 Sass 生成。子系统通过 Sass mixin 暴露可复用的样式声明组,组件在其 Sass 文件中导入这些 mixin,最终每个包将自己的 Sass 文件编译为单个 CSS 文件 |
| HTML | MDC Web不提供任何 HTML 模板,只通过文档给出组件所必需的 HTML 结构。这保证了它在任何框架、任何服务端渲染方案下都不抢走你的标记控制权 |
| JavaScript | 每个动态组件拆分为 Foundation 与 Adapter 两部分,便于将业务逻辑复用到 React、Angular 等多种 Web 平台 |
Foundation / Adapter / Vanilla Component 三层 JS 结构
这是 MDC Web 最值得理解的设计。每个动态组件由三部分协作:
- Foundation(基础):承载最能代表 Material Design 的业务逻辑,完全不引用任何 DOM 元素。凡涉及 DOM 操作的逻辑,都委托给 Adapter 方法完成。
- Adapter(适配器):一个接口,声明 Foundation 实现业务逻辑所需的全部方法。因为可以存在多种 Adapter 实现,所以组件能跨框架互操作。目前仓库只实现了原生 JavaScript 版本的 Adapter。
- Vanilla Component(原生组件):以一个根 Element 实例化,通过覆写
MDCComponent的getDefaultFoundation方法创建带 Vanilla Adapter 的 Foundation 实例;Vanilla Adapter 实现 Adapter API 并直接引用根元素。组件同时对外暴露开发者需要访问的 Foundation 方法的代理。
仓库中 packages/mdc-base/component.ts 给出了MDCComponent的骨架:构造函数依次调用initialize(...args)、通过getDefaultFoundation()创建 Foundation 并执行this.foundation.init(),再调用initialSyncWithDOM()完成与 DOM 的初始同步;destroy()则委托给foundation.destroy()释放资源。而 packages/mdc-base/foundation.ts 中的MDCFoundation基类定义了cssClasses、strings、numbers、defaultAdapter四个静态钩子与init()/destroy()生命周期方法,子类在此基础上实现具体业务逻辑。
对只想消费 MDC Web(而不是编写封装库)的开发者而言,只需要与Component交互,不必直接访问 Foundation 或 Adapter 的 API。
TypeScript 与发布产物
MDC Web 组件使用 TypeScript 编写(见 docs/code/architecture.md),以提升开发效率并减少错误。npm 发布产物包括:
- UMD JavaScript 包;
- 仅含 ES5 语法的 ES Module;
- 面向 TypeScript 用户的
.d.ts类型声明文件。
快速开始(一):CDN 一行引入,零构建跑起来
如果你想以最小的配置快速体验 MDC Web,直接通过 CDN 加载预编译的一体化 CSS 与 JS 包即可(以下代码来自仓库 README.md 的 Quick Start 示例,以文本输入框组件为例):
<!-- Required styles for Material Web --> <link rel="stylesheet" href="https://unpkg.com/material-components-web@latest/dist/material-components-web.min.css"> <!-- Render textfield component --> <label class="mdc-text-field mdc-text-field--filled"> <span class="mdc-text-field__ripple"></span> <span class="mdc-floating-label" id="my-label">Label</span> <input type="text" class="mdc-text-field__input" aria-labelledby="my-label"> <span class="mdc-line-ripple"></span> </label> <!-- Required Material Web JavaScript library --> <script src="https://unpkg.com/material-components-web@latest/dist/material-components-web.min.js"></script> <!-- Instantiate single textfield component rendered in the document --> <script> mdc.textField.MDCTextField.attachTo(document.querySelector<HTMLElement>('.mdc-text-field')); </script>几个要点:
- CSS 与 JS 分离加载:
material-components-web.min.css提供全部组件样式;material-components-web.min.js提供全部组件逻辑; - 全局命名空间
mdc:UMD 产物将组件挂载在window.mdc下,mdc.textField.MDCTextField即可取到文本输入框组件类; attachTo静态方法:这是MDCComponent提供的统一入口(见 packages/mdc-base/component.ts),子类覆写后即可用根元素一键实例化组件;- 标记结构即文档结构:
mdc-text-field--filled是填充型变体,mdc-text-field__ripple、mdc-floating-label、mdc-line-ripple分别是涟漪、浮动标签与底线波纹的必需子元素。
快速开始(二):npm 安装单个组件(以 textfield 为例)
工程化场景下,更推荐按需安装单个组件包。以下是 README.md 中 NPM 快速开始部分的完整复现(假设你已配置 webpack 将 Sass 编译为 CSS;完整配置见下一节与 docs/getting-started.md)。
安装 textfield 模块:
npm install @material/textfieldHTML:文本输入框的标准标记(更多选项见 packages/mdc-textfield 组件页):
<label class="mdc-text-field mdc-text-field--filled"> <span class="mdc-text-field__ripple"></span> <input type="text" class="mdc-text-field__input" aria-labelledby="my-label"> <span class="mdc-floating-label" id="my-label">Label</span> <span class="mdc-line-ripple"></span> </label>CSS:在 Sass 入口中引入组件所需样式并调用core-styles:
@use "@material/floating-label/mdc-floating-label"; @use "@material/line-ripple/mdc-line-ripple"; @use "@material/notched-outline/mdc-notched-outline"; @use "@material/textfield"; @include textfield.core-styles;这里可以看到架构设计中"子系统"的体现:textfield 依赖 floating-label(浮动标签)、line-ripple(底线波纹)、notched-outline(缺口描边)三个子系统,各自以mdc-*入口文件的形式被单独引入。与 packages/mdc-textfield/README.md 中 Styles 一节的写法完全一致。
JavaScript:导入MDCTextField并实例化:
import {MDCTextField} from '@material/textfield'; const textField = new MDCTextField(document.querySelector<HTMLElement>('.mdc-text-field'));这会在页面中第一个.mdc-text-field元素上初始化文本输入框组件。
从零搭建完整构建环境:Webpack + Sass + ES2015
上一节的 npm 用法依赖一个能编译 Sass 与 ES2015 的构建环境。仓库 docs/getting-started.md 给出了从零开始的完整五步流程,这里完整继承并给出可直接复制的配置。
Step 1:Webpack 编译 Sass
先执行npm init创建package.json,并在scripts中添加启动命令:
{ "scripts": { "start": "webpack serve" } }安装以下开发依赖(各司其职):
webpack:打包 Sass 与 JavaScript;webpack-dev-server:开发服务器;sass-loader:Webpack 中预处理 Sass 文件的 loader;sass:Sass 编译器(Dart Sass);css-loader:解析 CSS 的@import与url()路径;extract-loader:将 CSS 提取为.css文件;file-loader:将.css文件作为公共 URL 提供。
npm install --save-dev webpack webpack-cli webpack-dev-server css-loader sass-loader sass extract-loader file-loader创建引用bundle.css的index.html:
<!DOCTYPE html> <html> <head> <link rel="stylesheet" href="bundle.css"> </head> <body>Hello World</body> </html>创建app.scss:
body { color: blue; }配置webpack.config.js,将app.scss编译为bundle.css:
module.exports = [{ entry: './app.scss', output: { // This is necessary for webpack to compile // But we never use style-bundle.js filename: 'style-bundle.js', }, module: { rules: [ { test: /\.scss$/, use: [ { loader: 'file-loader', options: { name: 'bundle.css', }, }, { loader: 'extract-loader' }, { loader: 'css-loader' }, { loader: 'sass-loader', options: { // Prefer Dart Sass implementation: require('sass'), // See https://github.com/webpack-contrib/sass-loader/issues/804 webpackImporter: false, }, }, ] } ] }, }];运行npm start并打开 http://localhost:8080,即可看到蓝色的 "Hello World"。
Step 2:接入组件样式与主题 mixin
安装按钮组件:
npm install @material/button用以下内容替换app.scss——它同时演示了 MDC Web 的主题定制能力(通过 Sass mixin 覆写容器填充色):
@use '@material/button/mdc-button'; @use '@material/button'; .foo-button { @include button.container-fill-color(darksalmon); }要让 sass-loader 理解@material开头的导入,需要为sass-loader配置includePaths: ['./node_modules']:
{ loader: 'sass-loader', options: { // Prefer Dart Sass implementation: require('sass'), // See https://github.com/webpack-contrib/sass-loader/issues/804 webpackImporter: false, sassOptions: { includePaths: ['./node_modules'] }, } }只要所有 MDC Web 包保持同步更新,配置
includePaths通常就足够了;若因嵌套node_modules出现编译问题,见文末附录的自定义 importer 方案。
为了给 Sass 产物补充浏览器厂商前缀,还需通过 PostCSS 接入autoprefixer:
npm install --save-dev autoprefixer postcss-loader在webpack.config.js顶部引入,并在 loader 链中加入postcss-loader:
const autoprefixer = require('autoprefixer');{ loader: 'extract-loader' }, { loader: 'css-loader' }, { loader: 'postcss-loader', options: { postcssOptions: { plugins: [ autoprefixer() ] } } }, { loader: 'sass-loader', options: { sassOptions: { includePaths: ['./node_modules'] }, // Prefer Dart Sass implementation: require('sass'), // See https://github.com/webpack-contrib/sass-loader/issues/804 webpackImporter: false, } },@material/button的必需 HTML 结构见 packages/mdc-button/README.md,更新index.html加入按钮标记与foo-button类:
<body> <button class="foo-button mdc-button"> <div class="mdc-button__ripple"></div> <span class="mdc-button__label">Button</span> </button> </body>再次运行npm start打开 http://localhost:8080,即可看到一个被darksalmon填充的 Material Design 按钮:
Step 3:Webpack 编译 ES2015
通过 Babel 将 ES2015 编译为标准 JavaScript,安装依赖:
npm install --save-dev @babel/core babel-loader @babel/preset-env在index.html的</body>前加入脚本引用:
<script src="bundle.js" async></script>创建app.js:
console.log('hello world');修改webpack.config.js三处:
- 将 entry 改为同时包含
app.scss与app.js:entry: ['./app.scss', './app.js'] - 将
output.filename改为bundle.js:output: { filename: 'bundle.js', } - 在 rules 数组中追加
babel-loader:{ test: /\.js$/, loader: 'babel-loader', query: { presets: ['@babel/preset-env'], }, }
合并后的完整webpack.config.js如下(可直接复制使用):
const autoprefixer = require('autoprefixer'); module.exports = { entry: ['./app.scss', './app.js'], output: { filename: 'bundle.js', }, module: { rules: [ { test: /\.scss$/, use: [ { loader: 'file-loader', options: { name: 'bundle.css', }, }, {loader: 'extract-loader'}, {loader: 'css-loader'}, { loader: 'postcss-loader', options: { postcssOptions: { plugins: [ autoprefixer() ] } } }, { loader: 'sass-loader', options: { // Prefer Dart Sass implementation: require('sass'), // See https://github.com/webpack-contrib/sass-loader/issues/804 webpackImporter: false, sassOptions: { includePaths: ['./node_modules'], }, }, } ], }, { test: /\.js$/, loader: 'babel-loader', query: { presets: ['@babel/preset-env'], }, } ], }, };再次npm start,控制台应输出 "hello world"。
Step 4:接入组件 JavaScript(以 ripple 为例)
安装涟漪组件:
npm install @material/ripple在app.js中导入MDCRipple并初始化:
import {MDCRipple} from '@material/ripple/index'; const ripple = new MDCRipple(document.querySelector<HTMLElement>('.foo-button'));为什么要显式引用
/index?文档说明:显式引用index是为了直接导入每个包内的 ES2015 源码,从而支持 tree-shaking,并避免公共依赖(如 Ripple)产生重复代码;代价是你的构建工具链需要先转译这些 MDC Web 模块(即 Step 3 中安装的 Babel 工具链)。
再次npm start,按钮上就会出现 Material Design 的水波纹交互效果:
Step 5:构建生产产物
webpack-dev-server只适合开发预览,不适合生产环境。在package.json中新增构建脚本:
"scripts": { "build": "webpack", "start": "webpack serve" }执行:
npm run build这会在项目目录下生成bundle.js与bundle.css——即编译后的 CSS 与转译后的 JS,可直接复制到任意 Web 服务器托管的目录中。
附录:为嵌套 node_modules 配置 Sass importer
如果安装了冲突版本的各 MDC Web 包,可能出现嵌套的node_modules目录,导致上面的includePaths方案失效(Sass 只会在顶层node_modules中查找@material包)。此时可实现一个基于 Node 模块解析算法的自定义 importer,在webpack.config.js顶部(exports之前)添加:
const path = require('path'); function tryResolve_(url, sourceFilename) { // Put require.resolve in a try/catch to avoid node-sass failing with cryptic libsass errors // when the importer throws try { return require.resolve(url, {paths: [path.dirname(sourceFilename)]}); } catch (e) { return ''; } } function tryResolveScss(url, sourceFilename) { // Support omission of .scss and leading _ const normalizedUrl = url.endsWith('.scss') ? url : `${url}.scss`; return tryResolve_(normalizedUrl, sourceFilename) || tryResolve_(path.join(path.dirname(normalizedUrl), `_${path.basename(normalizedUrl)}`), sourceFilename); } function materialImporter(url, prev) { if (url.startsWith('@material')) { const resolved = tryResolveScss(url, prev); return {file: resolved || url}; } return {file: url}; }再将sass-loader配置更新为:
{ loader: 'sass-loader', options: { // Prefer Dart Sass implementation: require('sass'), // See https://github.com/webpack-contrib/sass-loader/issues/804 webpackImporter: false, sassOptions: { importer: materialImporter, includePaths: ['./node_modules'], }, }, }该 importer 会从"发起导入的文件所在目录"开始按 Node 模块解析规则向上查找依赖,从而找到离导入文件最近的@material包。
JavaScript 的多种导入方式与实例化技巧
除了上文用到的 ES Module 导入,MDC Web 还支持多种模块消费方式(详见 docs/importing-js.md)。每种方式的适用场景不同,可按技术栈选择:
ES Modules(推荐用于现代构建链)
import {MDCFoo, MDCFooFoundation} from '@material/foo';MDC Web 包的main字段指向dist下的预编译 UMD 模块以最大化兼容性——构建工具通常默认node_modules中的依赖已是 ES5 而跳过转译。如果你想利用 tree-shaking 与 MDC Web 内部的依赖共享来减小产物体积,可以显式引用包的index.js:
import {MDCFoo, MDCFooFoundation} from '@material/foo/index';如果你的构建工具支持读取package.json的module字段(指向仅含 ES5 语法的 ES Module),使用 Webpack 或 Rollup 时无需写/index,继续用简短的@material/foo即可——但要确保工具链会像处理你自己的代码一样处理 MDC Web 的模块。
CommonJS(Node 环境)
const mdcFoo = require('@material/foo'); const MDCFoo = mdcFoo.MDCFoo; const MDCFooFoundation = mdcFoo.MDCFooFoundation;AMD(RequireJS 类加载器)
require(['path/to/@material/foo'], mdcFoo => { const MDCFoo = mdcFoo.MDCFoo; const MDCFooFoundation = mdcFoo.MDCFooFoundation; });Global / CDN(浏览器直用)
const MDCFoo = mdc.foo.MDCFoo; const MDCFooFoundation = mdc.foo.MDCFooFoundation;TypeScript 类型支持
若使用 TypeScript,MDC Web 包自带.d.ts文件。大多数情况下无需显式引用——编译器会通过package.json的types属性自动找到它们(dist目录下的.d.ts对应 UMD 模块,包内还有与每个 foundation/component/adapter 一一对应的.d.ts)。需要说明的是,发布包中刻意省略了.ts源文件,因为.d.ts与转译后的.js(UMD 或 ES Module 格式)已被广泛接受。
一次实例化多个元素
文档示例中常见的new MDCFoo(document.querySelector('.mdc-foo'))只会命中页面中第一个匹配元素(querySelector最多返回一个元素)。要为多个元素同时实例化,使用querySelectorAll:
const foos = [].map.call(document.querySelectorAll('.mdc-foo'), function(el) { return new MDCFoo(el); });声明式初始化:mdc-auto-init
对于静态网站、原型等追求简单便捷的场景,mdc-auto-init提供了一种基于 DOM 的声明式初始化方式(见 packages/mdc-auto-init/README.md):在组件根元素上添加data-mdc-auto-init属性并赋值为组件类名,页面底部调用mdc.autoInit()即可:
<label class="mdc-text-field mdc-text-field--filled" contenteditable="false">【免费下载链接】material-components-webModular and customizable Material Design UI components for the web
项目地址: https://gitcode.com/gh_mirrors/ma/material-components-web创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考