您好!
欢迎来到京东云开发者社区
登录
首页
博文
课程
大赛
工具
用户中心
开源
首页
博文
课程
大赛
工具
开源
更多
用户中心
开发者社区
>
博文
>
webpack打包组件配置(React版本)
分享
打开微信扫码分享
点击前往QQ分享
点击前往微博分享
点击复制链接
webpack打包组件配置(React版本)
京东ZERO团队
2021-02-08
IP归属:未知
1008720浏览
React
# webpack 这篇文章是以打包react插件的形式,介绍webpack的一些配置信息。如果写简单插件的话还是推荐使用rollup,但是可以用写插件的形式去学习一下webpack的一些东西。(适用于初中级webpack学者) ## 1.安装node和npm,新建文件夹,在文件夹中执行npm init命令,一直回车生成一个package.json文件如下: ``` { "name": "cobrandcard", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "author": "", "license": "ISC" } ``` package.json文件的作用: 1)只要项目中使用了npm,项目根目录中都会有一个packgae.json文件,它可以手动创建,也可以通过执行npm init 命令来生成。 2)一般该文件中会记录项目的配置信息,版本、项目名称、许可证和作者等等,也会记录所需要的各种模块的依赖,包括开发依赖和执行依赖,还有scripts字段(稍后解释) 3)当执行npm install命令时,npm就会根据该文件中dependencies 和devDependencies 中的模块来下载相应的项目依赖。 问题:dependencies 和devDependencies 的区别? 一个是生产依赖,一个是开发依赖,生产依赖就是程序中用到的包,比如程序运行需要用到react,别人用你的插件的时候需要安装react才能运行程序,所以用插件的时候会下载生产环境dependencies的包。 ## 2.在根目录下新建src文件夹,存放自己的代码片段。 index.html ```csharp <html> <head> <meta charset='UTF-8'> </head> <body> <div id='app'></div> <script src='.src/index.js'></script> </body> </html> ``` src/index.js ```csharp window.document.getElementById('app').innerText = 'hello, world!' ``` 打开index.html文件,就可以看到浏览器中显示hello world了。 ## 3.利用webpack打包代码 1)安装webpack和webpack-cli 安装:npm install webpack webpack-cli --save-dev webpack-cli作用:可以在命令行使用webpack命令 在根目录使用npx webpack,默认打包src目录下的index.js文件,并在根目录生成一个dist文件夹,存放打包后的代码。 2)手动配置webpack打包项 默认的webpack配置文件为:webpack.config.js或者webpackfile.js 在根目录下新建webpack.config.js: ```javascript const path = require('path'); module.exports = { mode:'development', entry:'./src/index.js', output:{ filename:'index.js', path:path.resolve(__dirname,'dist') } ``` 命令行运行:npx webpack,发现根目录多了dist文件夹(打包后的文件) 利用package.json文件scripts脚本命令,快速执行打包命令 ```javascript "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "build":"webpack --config webpack.config.js" }, ``` npm run build执行相同的打包命令。 ## 4.本地服务器webpack-dev-server **目的**:在本地调试代码,不用手动打开index.html文件,不用手动刷新页面。 webpack-dev-server能帮我们做什么? **作用**:比如在使用webpack-dev-server之前,我每修改一处代码,都需要刷新页面才能看到,如果想看打包后的代码是否显示正常,还需要重新打包,再刷新页面,才能看到,而且需要我们手动运行html文件。 在使用webpack-dev-server之后,它会帮我们在本地起一个简单的服务器,并且可以实现监测代码实时更新的功能,在配置热更新之后,还可以实现不刷新页面的情况下进行局部更新。 **安装**:`npm install webpack-dev-server --save-dev` 配置服务器信息: ```javascript devServer: { // 根目录下dist为基本目录 contentBase: path.join(__dirname, "dist"), // 自动压缩代码 compress: true, // 服务端口为1208 port: 1208, // 自动打开浏览器 open: true, host: "dev.jd.com", // publicPath: "/assets/", hot: true, }, ``` 配置script信息: ```csharp "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "build": "webpack --config webpack.config.js", "start":"webpack-dev-server" }, ``` 重新打包之后,运行npm start,发现并没有看到页面打印hello world,看到的是打包之后的文件夹,所以我们需要在dist文件夹下创建一个html文件。 需要安装html-webpack-plugin插件,来产生html文件。 ```javascript const htmlWebpackPlugin = require('html-webpack-plugin') plugins:[ //数组 存放所有webpack插件 new htmlWebpackPlugin({ template:'./index.html', filename:'index.html' }) ] ``` 执行打包npm run build操作,发现在dist文件夹里面生成了html文件 ```javascript <html> <head> <meta charset='UTF-8'> </head> <body> <div id='app'></div> <script src="main.js"></script></body> </html> ``` npm start :本地服务器开启,浏览器看到hello world。 ## 5.热更新(HMR) 之前的webpack-dev-server的配置只是实现了监听文件变化、自动打包,实时刷新页面的功能,但是我们如果想要实现热更新的话,还需要加上新的配置项,hot的配置项表示开启热更新,开启热更新的配置又需要用到hotModuleReplacementPlugin插件。 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20210121144724682.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl8zODIwODMxNA==,size_16,color_FFFFFF,t_70) 热更新配置之后,再修改css样式,保存发现页面没有进行刷新(是通过浏览器左上角的刷新按钮来观察的),直接局部更新视图,已经实现了热更新。 如果想实现修改js文件后热更新,要加一段业务代码: ![在这里插入图片描述](https://img-blog.csdnimg.cn/20210121144859412.png) 保证在实现热更新之后,需要通知到业务代码重新render一遍,实现页面视图变化。 ## 6.样式文件和图片文件的引入 我们在写UI插件的时候,还会用到css sass等样式文件、图片文件等,但是由于webpack是node写的,它是只能识别js类型的文件,其他类型的文件webpack无法识别.这个时候,我们就需要用loaders来把这些文件转换一下,使得webpack能够识别他们。 **1)引入css文件** 在src文件夹下新建index.css文件,给id为app的元素加点字体样式,npm start看下效果,页面报错,识别不了css文件。 安装:npm install style-loader css-loader --save-dev webpack.config.js 文件修改如下: ```javascript module:{ rules:[ { test:/\.css$/, use:['style-loader','css-loader'] //从右往左执行 } ] } ``` 重新打包并运行发现样式引入成功。 css-loader作用是解析import样式文件,style-loader作用是将样式添加到head标签当中。 **原理**:webpack用正则表达式的方式查找以css结尾的文件,并将他们都交给style-loader和css-loader。这样通过import引入的css文件在运行时就被转换为style标签并插入到html文件中。 **2)引入图片文件** 安装:npm install file-loader --save-dev ```javascript module:{ rules:[ { test:/\.css$/, use:['style-loader','css-loader'] }, { test:/\.(png|svg|gif|jpg)$/, use:'file-loader' } ] } ``` 问题:url-loader和file-loader之间的区别? ```javascript { test: /\.(png|jpg|gif)$/i, loader: 'url-loader', options: { limit: 8192, mimetype: 'image/png' } } ``` 当文件小于一定的大小时,我们会选择使用url-loader,它不会将图片文件单独打包,会将图片文件转换成base64的形式插入到css文件中,这样就会减少http请求的数量,减少损耗。url-loader会兼容file-loader,它会在文件大小小于8192的时候使用url-loader,大于的时候使用file-loader。 **3)引入样式前缀** **安装**:npm i -D postcss-loader autoprefixer **作用**:如果需要写一些css3的属性,比如transform等,我们希望webpack可以自动帮我们加上厂商前缀,便于兼容各个浏览器的版本。 根目录下新建postcss.config,js,配置如下: ```javascript module.exports = { plugins: [ require('autoprefixer')({ overrideBrowserslist: [ "Android 4.1", "iOS 7.1", "Chrome > 31", "ff > 31", "ie >= 8" ] }) ] }; ``` 我们希望加入的样式前缀需要覆盖安卓4.1的系统、ios 7.1的系统等。 webpack.config.js的配置: ![在这里插入图片描述](https://img-blog.csdnimg.cn/2021010415095718.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl8zODIwODMxNA==,size_16,color_FFFFFF,t_70#pic_center) 打包之后,我们发现css3属性都加上厂商属性了。 ## 7.webpack 插件(plugins) 插件作用:在webpack运行到某一个时刻的时候,帮助你做一些事情。 1)HtmlWebpackPlugin 生成一个html文件,并将打包后的文件引入该html 2)CleanWebapckPlugin 打包前删除所有上一次打包好的文件 3)BundleAnalyzerPlugin 用来进行打包性能分析的插件 4)HotModuleReplacementPlugin 热更新所需要的插件 ## 8.语法的转换(babel) **需求**:此时我们又有一些其他的需求,写插件的时候,我们可能要用到es6的语法和api,此时我们就需要用到babel了,在webpack打包之前,需要用babel转义一下。 **什么是babel?** babel是一个工具链,它能够将 ECMAScript 2015+ 版本的代码转换为向后兼容的 JavaScript 语法,使得代码能够在当前和旧版本的浏览器中运行。 Es6的变化主要分为两部分: 1)语法部分:比如箭头函数和解构函数 --用@babel/preset-env去处理 2)API部分:比如map和promise --用@babel/polyfill 去处理 **处理es6语法:** 安装以上插件之后,在根目录下新建一个babel.config.json文件,加入以下规则: ```javascript { "presets": [ [ "@babel/preset-env", { "targets": { "firefox": "60", }, "useBuiltIns": "usage", } ] ] } ``` 默认情况下,它会转换浏览器不兼容的所有的es6+语法,但是有时候我们不需要兼容所有的浏览器版本,所以可以通过target来设定最低兼容的浏览器版本,这段代码的意思就是当firefox版本大于60的时候才进行转码,意思就是转换es6语法来兼容firefox60及以上的浏览器版本,还需要在webpack.config.js中加入babel-loader. **业务场景处理es6 API** ![在这里插入图片描述](https://img-blog.csdnimg.cn/20210121153129983.png) 用来处理es6 api的就是polyfill垫片,在文件的开头引入它就可以转换es6 api了,但是打包之后发现文件体积变大了好几倍,他把所有的包都引进来了,我们可以实现按需引入吗? 这个时候我们就用到了useBuiltIns属性,设置成usage之后,它表示按需引入,它会将我们程序中用到的ie8以上不支持的es6属性 通过全局变量的形式引入,兼容所有的api和原型方法,之所以还要加上corejs的版本为3,一是要声明corejs的版本,不然打包时会报错,二是corejs3改进了2的一些不足,可以兼容includes等原型方法。 **插件场景处理es6 API** 第二种配置方法的应用场景是插件或者框架,它是通过plugin-transform-runtime 去实现的。 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20210121153411780.png) 为何要区分两种使用场景处理es6 API? 第一种方法是以全局变量的形式引入代码包,会造成全局变量的污染,业务场景中并不怕全局变量的污染。但是在插件的场景中,别人使用我们的插件时,我们不希望给别人的使用环境造成污染,所以选择使用第二种(会形成沙箱环境,与全局环境相隔离)。 ## 9.加入react **需求**:最后一部分,因为我们要写一个react的插件,但是现在webpack还没有办法识别jsx语法,我们就来配置一下。 主要加的配置就是在babel的配置文件中加入@babel/preset-react 这个插件集合,然后在webpack配置文件中使用babel-loader,用babel-loader去处理jsx语法,这样就可以使用react了 ```javascript "@babel/preset-react" ``` 这样就可以在项目中运行react代码。(当然也要安装react和react-dom喽) ## 10.生产环境打包 现在我们已经写完了一个插件,并在本地进行联调。接下来,我们要将它打包上传。 新建一个文件:webpack.config.product.js ```javascript const path = require('path'); module.exports = { mode: 'production', entry: './src/index.js', output: { libraryTarget: 'umd', filename: 'index.js', path: path.resolve(__dirname, 'build'), chunkFilename: '[name].min.js' }, externals: { 'react': 'react', 'react-dom': 'react-dom' }, module: { rules: [ { test: /\.css$/, use: ['style-loader', 'css-loader'] }, { test: /\.(png|svg|gif|jpg)$/, use: 'file-loader' }, { test: /\.jsx?$/, loader: 'babel-loader', exclude: /node_modules/ } ] } } ``` libraryTarget 和 library 是开发类库必须要用的输出属性。 我们开发类库时希望别人用什么方式引入呢?引入的方式有以下几种: 传统的script方式: ```javascript <script src="demo.js"></script> ``` AMD方式: ```javascript define(['demo'], function(demo) { demo(); }); ``` commonjs方式: ```javascript const demo = require('demo'); demo(); ``` ES6 模块引入 ```javascript import demo from 'demo'; ``` 类库为什么支持不同方式的引入?这就是webpack.library和output.libraryTarget提供的功能。 output.libraryTarget 属性是控制webpack打包的内容如何被暴露的。 暴露的方式分为以下三种方式: 一.暴露一个变量 libraryTarget: "var" ```javascript output: { libraryTarget:'var', library:'abc', filename: "index.js", path: path.resolve(__dirname, "build"), }, ``` webpack打包出来的值赋值给一个变量,该变量名就是output.library指定的值。 以下是webpack打包后的一部分内容: ![在这里插入图片描述](https://img-blog.csdnimg.cn/2021011114181848.png#pic_center) 将打包后的内容复制给一个全局变量,引用类库的时候直接使用该变量,nodejs环境不支持。 ```javascript <head> <meta charset='UTF-8'> <script src='./build/index.js'></script> <script> console.log(abc.mytest()) </script> </head> ``` 二.通过对象属性暴露 库的返回值分配给指定对象的指定属性。属性由output.library指定,对象由output.libraryTarget指定。 1)`libraryTarget: "this"` ```javascript this["myDemo"] = _entry_return_; this.myDemo(); myDemo(); ``` 2)`libraryTarget: "window"` ```javascript window["myDemo"] = _entry_return_; window.myDemo.doSomething(); ``` 3)`libraryTarget: "global"` global["myDemo"] = _entry_return_; 下面这种情况,可以支持nodejs环境。 ```javascript mode: "production", entry: "./src/index.js", target:'node', output: { libraryTarget:'global', library:'abc', filename: "index.js", path: path.resolve(__dirname, "build"), }, ``` 以上三种方法是在公共对象上export出你的方法函数。 优点:减少变量冲突 缺点:nodejs环境不支持 三. 通过模块暴露 1. `libraryTarget: "commonjs"` ```javascript exports["myDemo"] = _entry_return_; require("myDemo").doSomething(); ``` 直接在exports对象上导出--定义在library上的变量,node支持,浏览器不支持 ```javascript <head> <meta charset='UTF-8'> <script src='./build/index.js'></script> <script> console.log(require('abc').mytest()) </script> </head> ``` ![在这里插入图片描述](https://img-blog.csdnimg.cn/20210113104216667.png#pic_center) 这个选项可以使用在commonjs环境中。 2) `libraryTarget: "commonjs2"` ```javascript module.exports = _entry_return_; const myDemo = require("myDemo"); myDemo(); ``` 直接用module.exports导出,会忽略library变量,node支持,浏览器不支持,这个选项可以使用在commonjs环境中。 为什么commonjs不需要单独引入requirejs? commonjs是服务端模块化语言规范,在node中使用的时候会使用node中的requireJS。 3) `libraryTarget: "amd"` ```javascript define("myDemo", [], function() { return _entry_return_; }); ``` ```javascript require(['myDemo'], function(myDemo) { // Do something with the library... myDemo(); }); ``` ```javascript <head> <meta charset='UTF-8'> <script src='./build/index.js'></script> <script> require(['abc'], function (mytest) { // Do something with the library... console.log(mytest()); }); </script> </head> ``` ![在这里插入图片描述](https://img-blog.csdnimg.cn/20210113104939406.png#pic_center) amd属于客户端模块语言的规范,需要用户自己引入requirejs才能使用。不支持nodejs环境,支持浏览器环境。 4) `libraryTarget: "umd"` 该方案支持commonjs、commonjs2、amd,可以在浏览器、node中通用。它会根据引用该插件的上下文来判断属于什么环境,使其和CommonJS、AMD兼容或者暴露为全局变量。 ```javascript (function webpackUniversalModuleDefinition(root, factory) { if(typeof exports === 'object' && typeof module === 'object') module.exports = factory(); else if(typeof define === 'function' && define.amd) define([], factory); else if(typeof exports === 'object') exports["MyLibrary"] = factory(); else root["MyLibrary"] = factory(); })(typeof self !== 'undefined' ? self : this, function() { return _entry_return_; }); ``` ```javascript output: { library: { root: "myDemo", amd: "my-demo", commonjs: "my-common-demo" }, libraryTarget: "umd" } ``` 最后建议,如果目标明确,我只是兼容nodejs,那么选择commonjs/commonjs2,如果只兼容浏览器,那就选择暴露变量的方式,如果想通用,那就选择umd的方式,对于不同的情况做多种处理方式,是非常明智的选择。 ## 11.在保存react状态的前提下进行热更新 在使用热更新之后,我们发现在一个文件中修改某一个内容,这个文件会重新render一次,那么该文件中的一些状态,比如react经典的计数器,就会被重置。 见下图,backtop文件修改之后,会render整个组件,状态重置。 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20210121113755600.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl8zODIwODMxNA==,size_16,color_FFFFFF,t_70#pic_center) 这样才能做到在保存react状态的前提下进行热更新? 安装:npm install react-hot-loader @hot-loader/react-dom --save-dev babel.config.json:添加以下代码 ```javascript "plugins": ["react-hot-loader/babel"] ``` backtop.js 用react-hot-loader包一层: ```javascript import { hot } from "react-hot-loader/root"; //..... export default hot(App); ``` 即可实现在保存react状态的前提下进行热更新。 以上:我们实现了基本的webpack打包实现react插件的配置,如果想要实现其他的功能,可以依次叠加。webpack路途遥远,祝愿大家成为一名坚强的‘高级webpack配置师’。
原创文章,需联系作者,授权转载
上一篇:京东App Swift 混编及组件化落地
下一篇:webpack核心概念与基本实现
相关文章
【技术干货】企业级扫描平台EOS关于JS扫描落地与实践!
开发也要防沉迷--IDEA插件教程
京东mPaaS平台之Android组件化系统私有化部署改造实践!
京东ZERO团队
文章数
39
阅读量
91754
作者其他文章
01
webpack打包组件配置(React版本)
这篇文章是以打包react插件的形式,介绍webpack的一些配置信息。如果写简单插件的话还是推荐使用rollup,但是可以用写插件的形式去学习一下webpack的一些东西。(适用于初中级webpack学者)
01
webpack核心概念与基本实现
webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)。当 webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle。**
01
Typescript合成Webpack中
TypeScript是JavaScript类型的超集,它可以编译成纯JavaScript,简称ts。相对于ES6,TypeScript最大的改善是增加了类型系统,国内外很多大型工程都用它,如AngularJs,白鹭引擎、Antd。
01
小程序加载svg图片
小程序的[组件](https://developers.weixin.qq.com/miniprogram/dev/component/)中是没有支持`SVG`标签的。 但是在前端小伙伴的实际开发中,UED经常提供SVG图片过来,如果不想用引入`iconfont`的话,那么妹子我将介绍个很好用的方法。
京东ZERO团队
文章数
39
阅读量
91754
作者其他文章
01
webpack核心概念与基本实现
01
Typescript合成Webpack中
01
小程序加载svg图片
添加企业微信
获取1V1专业服务
扫码关注
京东云开发者公众号