利用webpack搭建脚手架一套完整流程
栏目: JavaScript · 发布时间: 5年前
内容简介:我们的目标是利用webpack搭建一个基于react + react-router +dva + es6 + less + antd用于中后台开发的脚手架,同学们可能会说社区里那么多优秀的脚手架为什么还要自己搭,而且网络上这类文章也非常的多,没有再重复写的必要,但是对我而言,分享也是再次学习的过程,只有自己动手实现一遍才会使印象更加深刻,总的来说基本秉持一个理念:在学习中分享,在分享中学习。新建index.js和index.html文件:安装webpack
我们的目标是利用webpack搭建一个基于react + react-router +dva + es6 + less + antd用于中后台开发的脚手架,同学们可能会说社区里那么多优秀的脚手架为什么还要自己搭,而且网络上这类文章也非常的多,没有再重复写的必要,但是对我而言,分享也是再次学习的过程,只有自己动手实现一遍才会使印象更加深刻,总的来说基本秉持一个理念:在学习中分享,在分享中学习。
初始化项目
mkdir React-Whole-barrels // 建立项目文件夹,名称随意 cd React-Whole-barrels mkdir src // 新建src文件夹 npm init -y //初始化项目 复制代码
新建index.js和index.html文件:
// index.js console.log('hello world') // index.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>React-Whole-barrels</title> </head> <body> <div id='root'></div> </body> </html> 复制代码
安装webpack并配置
安装webpack
npm add webpack webpack-cli --save-dev 复制代码
新建一个build文件夹存放wbepack配置文件
mkdir build touch webpack.dev.js 复制代码
webpack.dev.js,并书写基本的 配置
const path = require('path'); module.exports = { mode: 'development', // 模式,表示dev环境 entry: './src/index.js', // 入口文件 module: {}, // 让 webpack 能够去处理那些非 JavaScript 文件 plugins: [], // 插件 output: { filename: 'bundle.js', // 打包后文件名称 path: path.resolve(__dirname, '../dist') // 打包后文件夹存放路径 } } 复制代码
package.json更改
"scripts": { "start": "webpack --config ./build/webpack.dev.js" }, 复制代码
配置babel
既然要使用react和es6,babel的配置是必不可少的
npm install @babel/polyfill core-js@2 --save // @babel/polyfill: 模拟一个es6+的环境,提供es6方法和函数的垫片 // core-js@2:@babel/preset-env实现按需引入polyfill时,声明core-js版本 npm install babel-loader @babel/core @babel/preset-env @babel/preset-react --save-dev // babel-loader和@babel/core是核心模块 // @babel/preset-env是一个智能预设,允许您使用最新的JavaScript // @babel/preset-react 转换JSX 复制代码
扩展:如果是开发 工具 库,想要实现按需替换,可以使用下面下面两个工具来实现 @babel/plugin-transform-runtime避免 polyfill 污染全局变量,减小打包体积,因此更适合作为开发工具库 @babel/runtime-corejs2作为生产环境依赖,约等于@babel/runtime + babel-polyfill,使用了@babel/runtime-corejs2,就无需再使用@babel/runtime了
.babelrc文件
{ "presets": [ [ "@babel/preset-env", { // 将es6的语法翻译成es5语法 "targets": { "chrome": "67", }, "useBuiltIns": "usage", // 做@babel/polyfill补充时,按需补充,用到什么才补充什么, "corejs": "2", } ], "@babel/preset-react", ], "plugins": [ "@babel/plugin-proposal-class-properties" ] } 复制代码
1.解决:Support for the experimental syntax 'classProperties' isn't currently enabled 问题, npm i -D @babel/plugin-proposal-class-properties,并在plugins中配置。 2.useBuiltIns 和 transform-runtime 不能同时使用,如果使用transform-runtime就不要配useBuiltInsor,一般独立的类库项目才用transform-runtime,出处
更改webpack.dev.js
... module: { rules: [{ test: /\.js$/, exclude: /node_modules/, // 排除node_modules中的代码 use: [{ loader: 'babel-loader' }], }] }, 复制代码
安装react
安装react、react-dom、react-router,并书写代码
npm install react react-dom react-router react-router-dom --save 复制代码
webpack-dev-server
书写完成上述代码运行npm start后,打开index.html你会发现没有任何内容,此时我们需要配置一个简单的WEB服务器,指向index.html。
// 安装webpack-dev-server npm install webpack-dev-server --save-dev // 配置webpack.dev.js ... devServer: { contentBase: path.join(__dirname, '../dist') }, ... // 更改npm start 命令 "start": "webpack-dev-server --config ./build/webpack.dev.js" 复制代码
运行npm start命令,既可以看到我们的代码内容。dev更多配置请看 这里
使用html-webpack-plugin和clean-webpack-plugin插件
到目前为止,我们会发现都需要手动都将index.html放到dist文件夹中,并手动引入bundle.js.这个问题可以通过html-webpack-plugin解决。
引入html-webpack-plugin后,在plugins生成一个实例,HtmlWebpackPlugin可以接受一个参数作为模版文件,打包结束后自动生成一个以参数为模版的html文件。并把打包生成的js文件自动引入到html文件中。clean-webpack-plugin可以实现在每次打包之前都把上一次的打包文件清空,这样避免了冗余文件的存在,用法也是直接在plugins里面生成一个实例.
// 安装html-webpack-plugin和clean-webpack-plugin npm install html-webpack-plugin clean-webpack-plugin --save-dev 复制代码
更改webpack.dev.js
const HtmlWebpackPlugin = require('html-webpack-plugin'); const { CleanWebpackPlugin } = require('clean-webpack-plugin'); ... plugins: [ // 插件 new HtmlWebpackPlugin({ // 向dist文件中自动添加模版html template: 'src/index.html', }), new CleanWebpackPlugin(), // 打包后先清除dist文件,先于HtmlWebpackPlugin运行 ], 复制代码
less配置
样式使用less预处理器,那么就需要使用less,less-loader,css-loader,style-loader等
npm install less less-loader css-loader style-loader postcss-loader --save-dev // less-loader 编译less // css-loader // 编译css // style-loader创建style标签,并将css添加进去 // postcss-loader提供自动添加厂商前缀的功能,但是需要配合autoprefixer插件来使用 npm install autoprefixer --save-dev 复制代码
更改webpack.dev.js配置
rules: [ ... { test: /\.less$/, exclude: /node_modules/, use: ['style-loader', { loader: 'css-loader', options: { importLoaders: 2 } }, 'less-loader', 'postcss-loader'] }, { test: /\.css$/, use: ['style-loader', 'css-loader', 'postcss-loader'] } ] 复制代码
postcss.config.js
module.exports = { // 自动添加css厂商前缀 plugins: [ require('autoprefixer') ] } 复制代码
图标和图片的处理
安装file-loader和url-loader
npm install file-loader url-loader --save-dev 复制代码
filr-loader帮助我们做两件事情:
1.当遇到图片文件时会将其打包移动到dist目录下
2.接下来会获得图片模块的地址,并将地址返回到引入模块到变量之中
url-loader基本上可以实现file-loader的功能,但是有一区别就是经过url-laoder打包后的dist文件下是不存在image文件的,这是因为url-loader会把图片转换成base64的字符串直接放在bundle.js里面。
好处:直接将图片打包到js里,不用额外到请求图片,省了http请求
坏处:如果遇到打包到文件非常大,那么加载会加载很长时间,影响体验
因此我们可以这样配置webpack.dev.js
rules: [ ... { test: /\.(png|jpg|gif|jpeg)$/, use: { loader: 'url-loader', options: { name: '[name]_[hash].[ext]', // placeholder 占位符 outputPath: 'images/', // 打包文件名 limit: 204800, // 小于200kb则打包到js文件里,大于则使用file-loader的打包方式打包到imgages里 }, }, }, { test: /\.(eot|woff2?|ttf|svg)$/, use: { loader: 'url-loader', options: { name: '[name]-[hash:5].min.[ext]', // 和上面同理 outputPath: 'fonts/', limit: 5000, } }, } ] 复制代码
模块热替换HMR
模块热替换也称为HMR,代码更新时只会更新被修改部分都显示。有如下有点
- 针对于样式调试更加方便
- 只会更新被修改代码的那部分显示,提升开发效率
- 保留在完全重新加载页面时丢失的应用程序状态。
HMR配置有两种方式,一种cli方式,一种Node.js API方式,我们这里采用第二种方式,如果想了解两种HMR的实现以及HMR实现原理可以看这里。
我们通过在自定义开发服务下,使用插件webpack-dev-middleware和webpack-hot-middleware配合实现HMR
// 安装webpack-dev-middleware webpack-Hot-middleware npm install webpack-dev-middleware webpack-hot-middleware --save-dev // 不要忘记安装express,我们是通过express来启动本地服务 npm install express --save-dev 复制代码
新建dev-server.js
const path = require('path'); const express = require('express'); const webpack = require('webpack'); const webpackDevMiddleware = require('webpack-dev-middleware'); const webpackHotMiddleware = require("webpack-hot-middleware") const config = require('./webpack.dev.js'); const complier = webpack(config); // 编译器,编译器执行一次就会重新打包一下代码 const app = express(); // 生成一个实例 const DIST_DIR = path.resolve(__dirname, '../', 'dist'); // 设置静态访问文件路径 let devMiddleware = webpackDevMiddleware(complier, { quiet: true, noInfo: true, stats: 'minimal' }) let hotMiddleware = webpackHotMiddleware(complier,{ log: false, heartbeat: 2000 }) app.use(devMiddleware) app.use(hotMiddleware) // 设置访问静态文件的路径 app.use(express.static(DIST_DIR)) app.listen(8080, () => { console.log("成功启动:localhost:"+ 8080) }) //监听端口 复制代码
更改webpack.dev.js,添加如下内容:
const webpack = require('webpack'); ... entry: { //实现刷新浏览器webpack-hot-middleware/client?noInfo=true&reload=true 是必填的 main: ['webpack-hot-middleware/client?noInfo=true&reload=true', './src/index.js'] }, ... plugins: [ ... new webpack.NamedModulesPlugin(), //用于启动HMR时可以显示模块的相对路径 new webpack.HotModuleReplacementPlugin(), // 开启模块热更新,热加载和模块热更新不同,热加载是整个页面刷新 ] 复制代码
webpack-hot-middleware更多配置在 这里
修改启动命令:
"start": "node ./build/dev-server.js" 复制代码
安装antd和dva
npm install antd babel-plugin-import --save 复制代码
安装antd后即可使用and-design里面的ui组件。使用babel-plugin-import来实现按需加载的效果
.babelrc
"plugins": [ ... ["import", { "libraryName": "antd", "libraryDirectory": "es", "style": "css" // `style: true` 会加载 less 文件 }] ] 复制代码
npm install dva --save 复制代码
成功安装后改写index.js和并新建router.js
// index.js import dva from 'dva'; // 1. Initialize const app = dva({}); // app.use(); // 2. Model // app.model(); // 3. Router app.router(require('./router').default); // 4. Start app.start('#root'); 复制代码
这里的代码量有点多就不一一列出,可以在github上面 查看
在使用react-router的过程中你可能会出现这样的问题,点击刷新后报错 or Cannot GET,解决方案有两个。 1.用的 BrowserRouter 改为 HashRouter 即可。2.devServer 中必须设置 historyApiFallback: true 由于我们使用的自定义服务,那么我们可以使用connect-history-api-fallback来实现和historyApiFallback相同的功能。具体实现看 代码
public path
CDN通过将资源部署到世界各地,使得用户可以就近访问资源,加快访问速度。如果我们把网页的静态资源上传到CDN服务上,在访问这些资源时,publicPath填写的就是CDN提供URL
我们当前用/,相对于当前路径,是因为我们的资源在同一文件夹下。》》
webpack.dev.js
output: { ... publicPath : '/' } 复制代码
使用sourcemap
sourceMap本质上是一种映射关系,打包出来的js文件中的代码可以映射到代码文件的具体位置,这种映射关系会帮助我们直接找到在源代码中的错误。可以直接在devtool中使用.合理的使用source-map可以帮助我们提高开发效率,更快的定位到错误位置。
生产环境和开发环境的devtool配置是不同的。我们可以在webpack.dev.js中添加devtool。
devtool:"cheap-module-eval-source-map",// 开发环境配置最佳实践 devtool:"cheap-module-source-map", // 生产配置最佳实践 复制代码
生产环境构建
到目前为止我们配置的都是开发环境的webpack,开发环境(development)和生产环境(production)的构建目标差异很大,而在生产环境中,我们的目标则转向于关注更小的 bundle,更轻量的 source map,以及更优化的资源,以改善加载时间.
新建webpack.prod.js
const path = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const { CleanWebpackPlugin } = require('clean-webpack-plugin'); module.exports = { mode: "production", // 只要在生产模式下, 代码就会自动压缩,自动启用 tree shaking devtool:"cheap-module-source-map", entry: { // 入口文件 main: './src/index.js' }, module: { rules: ...省略 //代码和dev中相同 }, plugins: [ new HtmlWebpackPlugin({ template: 'src/index.html', }), new CleanWebpackPlugin(), ], output: { publicPath: "/", filename: 'bundle.js', path: path.resolve(__dirname, '../dist') } } 复制代码
添加打包脚本,
"build": "webpack --config ./build/webpack.prod.js" 复制代码
执行npm run build后,你会发现dist文件夹下已经生成一系列文件。你会发现生产环境下的配置和开发环境下的配置有很多相同,接下来我们会对webpack配置进行优化。
提取公共配置
webpack.dev.js和webpack.prod.js中有很多相同对配置,我们可以将公共配置提取出来,再使用webpack-merge来将不同环境下的配置合并起来。
npm install webpack-merge --save 复制代码
webpack配置文件更改
webpack.dev.js
... const merge = require('webpack-merge'); const commonConfig = require('./webpack.common.js'); const devConfig = { mode: 'development', devtool:"cheap-module-eval-source-map", entry: { main: ['webpack-hot-middleware/client?noInfo=true&reload=true', './src/index.js'] }, devServer: { contentBase: path.join(__dirname, '../dist') }, plugins: [ new webpack.NamedModulesPlugin(), new webpack.HotModuleReplacementPlugin(), ], output: {} } module.exports = merge.smart(commonConfig, devConfig) 复制代码
webpack.prod.js
const merge = require('webpack-merge'); const commonConfig = require('./webpack.common.js'); const prodConfig = { mode: "production", // 只要在生产模式下, 代码就会自动压缩 devtool:"cheap-module-source-map", entry: { main: './src/index.js' }, module: {}, plugins: [], output: {} } module.exports = merge.smart(commonConfig, prodConfig) 复制代码
webpack.common.js
const path = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const { CleanWebpackPlugin } = require('clean-webpack-plugin'); const commonConfig = { module: { ...太多了省略吧 }, plugins: [ new HtmlWebpackPlugin({ template: 'src/index.html', }), new CleanWebpackPlugin(), ], output: { publicPath: "/", filename: 'bundle.js', path: path.resolve(__dirname, '../dist') } } module.exports = commonConfig; 复制代码
具体的可以看 这里 。
CSS文件代码分割
想要分开打包我们的css文件,需要使用mini-css-extract-plugin这个插件,但是这个插件目前还不支持HMR,为了不影响开发效率,因此就在生成环境下使用该插件。
optimize-css-assets-webpack-plugin 这个插件可以帮助我们把相同的样式合并。
css-split-webpack-plugin插件可以帮我们把过大的css文件拆分
npm install mini-css-extract-plugin optimize-css-assets-webpack-plugin css-split-webpack-plugin --save-dev 复制代码
修改webpack.prod.js,并同步修改webpack.common.js、webpack.dev.js 看 这里
const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin"); const CSSSplitWebpackPlugin = require('css-split-webpack-plugin').default; ... module: { rules: [{ test: /\.less$/, exclude: /node_modules/, use: [MiniCssExtractPlugin.loader, { loader: 'css-loader', options: { importLoaders: 2 } }, 'less-loader', 'postcss-loader'] }, { test: /\.css$/, use: [MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader'] }] }, optimization: { minimizer: [new OptimizeCSSAssetsPlugin({})] }, plugins: [ new MiniCssExtractPlugin({ filename: '[name].css', chunkFilename: '[name].chunk.css' }), new CSSSplitWebpackPlugin({ size: 4000, filename: '[name]-[part].[ext]' }) ] 复制代码
浏览器缓存(Cathing)
为了解决浏览器文件缓存问题,例如:代码更新后,文件名称未改变,浏览器非强制刷新后,浏览器去请求文件时认为文件名称未改变而直接从缓存中读取不去重新请求。我们可以在webpack.prod.js输出文件名称中添加hash值.
使用HashedModuleIdsPlugin的原因是可以当更改某一个文件时,只改变这一个文件的hash值,而不是所有的文件都改变。
plugins: [ ... new webpack.HashedModuleIdsPlugin(), //根据模块的相对路径生成一个四位数的hash new webpack.optimize.CommonsChunkPlugin({ // 配合上面的插件使用 name: 'runtime' }) ], output: { filename: '[name].[contenthash].js', // entry对应的key值 chunkFilename: '[name].[contenthash].js', // 间接引用的文件会走这个配置 }, 复制代码
运行npm run build命令后,会发现dist文件中js文件名中已经有了hash值
记得同步修改webpack.common.js、webpack.dev.js,如果你不知道如何修改,请看 这里
文件路径优化
extension配置之后可以不用在require或是import的时候加文件扩展名,会依次尝试添加扩展名进行匹配
mainFiles配置后不用加入文件名,会依次尝试添加的文件名进行匹配
alias通过配置别名可以加快webpack查找模块的速度。
webpack.common.js更改:
resolve: { extensions: ['.js', '.jsx'], // 当通过import login from './login/index'形式引入文件时,会先去寻找.js为后缀当文件,再去寻找.jsx为后缀的文件 mainFiles: ['index', 'view'], // 如果是直接引用一个文件夹,那么回去直接找index开头的文件,如果不存在再去找view开头的文件 // alias: { // 暂时没用到,就注释掉 // home: path.resolve(__dirname, '../src/home') // 配置别名可以加快webpack查找模块的速度 //} } 复制代码
Tree Shaking
Tree Shaking可以剔除掉一个文件中未被引用掉部分,并且只支持ES Modules模块的引入方式,不支持CommonJS的引入方式。原因:ES Modules是静态引入的方式,CommonJS是动态的引入方式,Tree Shaking只支持静态引入方式。
// webpack.dev.js optimization: { // 开发环境时使用 usedExports: true }, // package.json "sideEffects": [ "*.css" ], // 如果在项目中使用类似 css-loader 并 import 一个 CSS 文件,则需要将其添加到 side effect 列表中,以免在生产模式中无意中将它删除 复制代码
注意:mode 选项设置为production,可以自动启用 minification(代码压缩) 和 tree shaking
代码分割(SplitChunksPlugin)
不管是同步代码的分割还是异步的代码分割都可以使用SplitChunksPlugin这个插件,可以将第三方库从业务代码中分割出来.
webpack.prod.js
... optimization: { splitChunks: { chunks: "all", // 只对异步引入代码起作用,设置all时并同时配置vendors才对两者起作用 minSize: 30000, // 引入的库大于30kb时才会做代码分割 minChunks: 1, // 一个模块至少被用了1次才会被分割 maxAsyncRequests: 5, // 同时异步加载的模块数最多是5个,如果超过5个则不做代码分割 maxInitialRequests: 3, // 入口文件进行加载时,引入的库最多分割出3个js文件 automaticNameDelimiter: '~', // 生成文件名的文件链接符 name: true, // 开启自定义名称效果 cacheGroups: { // 判断分割出的代码放到那里去 vendors: { // 配合chunks: ‘all’使用,表示如果引入的库是在node-modules中,那就会把这个库分割出来并起名为vendors.js test: /[\\/]node_modules[\\/]/, priority: -10, filename: 'vendors.js' }, default: { // 为非node-modules库中分割出的代码设置默认存放名称 priority: -20, reuseExistingChunk: true, // 避免被重复打包分割 filename: 'common.js' } } } } 复制代码
DellPlugin提升打包速度
对于第三方库,这些库在很长的一段时间内,基本不会更新,打包的时候分开打包来提升打包速度, DllPlugin动态链接库插件,其原理就是把网页依赖的基础模块抽离出来打包到dll文件中,当需要导入的模块存在于某个dll中时,这个模块不再被打包,而是去dll中获取。
cnpm install add-asset-html-webpack-plugin fs --save // add-asset-html-webpack-plugin: 将打包生产的dll.js文件自动引入html // fs文件读取 复制代码
新建webpack.dll.js
const path = require('path'); const webpack = require('webpack'); module.exports = { mode: 'production', entry: { vendors: ['lodash'], react: ['react', 'react-dom'], }, output: { filename: '[name].dll.js', path: path.resolve(__dirname, '../dll'), library: '[name]', }, plugins: [ new webpack.DllPlugin({ name: '[name]', path: path.resolve(__dirname, '../dll/[name].manifest.json'), }), ], }; 复制代码
webpack.common.js更改
const fs = require('fs'); const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin'); ... const plugins = [ new HtmlWebpackPlugin({ template: 'src/index.html', }), new CleanWebpackPlugin(), ]; const files = fs.readdirSync(path.resolve(__dirname, '../dll')); files.forEach((file) => { if (/.*\.dll.js/.test(file)) { plugins.push(new AddAssetHtmlWebpackPlugin({ // 将dll.js文件自动引入html filepath: path.resolve(__dirname, '../dll', file), })); } if (/.*\.manifest.json/.test(file)) { plugins.push(new webpack.DllReferencePlugin({ // 当打包第三方库时,会去manifest.json文件中寻找映射关系,如果找到了那么就直接从全局变量(即打包文件)中拿过来用就行,不用再进行第三方库的分析,以此优化打包速度 manifest: path.resolve(__dirname, '../dll', file), })); } }); 复制代码
package.json
"dll": "webpack --config ./build/webpack.dll.js" 复制代码
PWA优化
PWA全称progressive Web Application,PWA实现的功能是即便服务器挂掉,还是可以通过在本地的缓存来访问到页面。首先安装workbox-webpack-plugin插件,然后在webpack.prod.js中Plugins中配置,因为该功能只在线上使用。
npm install workbox-webpack-plugin --save 复制代码
webpack.prod.js更改
const WorkboxPlugin = require('workbox-webpack-plugin'); ... plugins: [ ... new WorkboxPlugin.GenerateSW({ clientsClaim: true, skipWaiting: true }) ] 复制代码
运行命令打包后会多处两个文件precache-manifest.js和service-worker.js, service-worker这个文件就可以让我们的页面被缓存住
指定环境
可以通过指定环境,来使webpack进行选择性编译,择性编译是指根据打包是环境的不同,选择性地让特定的语句有效,让特定的语句无效。这样可以对具体用户的环境进行代码优化,从而删除或添加一些重要代码。
最简单的例子,在开发环境中,我们打印日志,但在生产环境中,我们让所有打印日志的语句无效(让程序不运行打印的语句,甚至让打包出来的文件根本就不包含打印日志的语句)
我们可以使用 webpack 内置的 DefinePlugin 来实现。
// webpack.dev.js ... plugins: [ ... new Webpack.DefinePlugin({ 'process.env.NODE_ENV': JSON.stringify('development'), }), ] // webpack.prod.js plugins: [ ... new Webpack.DefinePlugin({ 'process.env.NODE_ENV': JSON.stringify('production'), }), ] 复制代码
以上所述就是小编给大家介绍的《利用webpack搭建脚手架一套完整流程》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- React脚手架搭建
- 搭建webpack简易脚手架
- 搭建一个 nodejs 脚手架
- 搭建一个Koa后端项目脚手架
- React + TypeScript 搭建多Tab页脚手架
- 【手把手撸一个脚手架】第二步, 搭建开发环境
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
用户思维+:好产品让用户为自己尖叫
[美] Kathy Sierra / 石航 / 人民邮电出版社 / 2017-9 / 69.00元
畅销产品与普通产品的本质区别是什么?若没有巨额预算、不爱营销噱头、不开奢华的产品发布会,如何打造可持续成功的产品?本书针对上述问题提出了新颖的观点:用户并不关心产品本身有多棒,而是关心使用产品时自己有多棒。作者利用其多年的交互设计经验,生动阐释了这一观点背后的科学。可贵的是,本书并不止步于解释“为什么”,还清晰呈现了“怎么做”。 本书风格活泼、图文并茂,其对话式内容既引人入胜,又引人深思,适......一起来看看 《用户思维+:好产品让用户为自己尖叫》 这本书的介绍吧!
JS 压缩/解压工具
在线压缩/解压 JS 代码
图片转BASE64编码
在线图片转Base64编码工具