Webpack是什么 Webpack是JavaScript 应用程序的前端资源模块化管理 和静态模块打包器 。
它可以将许多松散的模块按照依赖和规则打包成符合生产环境部署的前端资源。还可以将按需加载的模块进行代码分隔,等到实际需要的时候再异步加载。通过 loader
的转换,任何形式的资源都可以视作模块,比如 CommonJs 模块、 AMD 模块、 ES6 模块、CSS、图片、 JSON、Coffeescript、 LESS 等。
简单来说,它可以将我们项目中的所有js、图片、css等资源,根据其入口文件的依赖关系,打包成一个能被浏览器识别的js文件 。能够帮助前端开发将打包的过程更智能化和自动化。
一个基本的webpack配置:
1 2 3 4 5 6 7 8 module .exports={ entry :, output:{}, module :{ rules :[] }, plugins :[], }
entry
是设置入口文件,webpack会根据其入口文件的依赖关系,将所有依赖关系的资源打包在一起。
output
是设置打包后文件的输出,例如:打包后的文件名和输出路径等。
module
是设置loader,即设置一些资源转换工具,例如将stylus、less、sass进行处理,转换成浏览器能够识别的css资源。
plugins
是设置插件的地方,官方也提供了很多插件,插件能够帮助我们做很多事情,例如压缩打包等
eg:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 const HtmlWebPackPlugin = require("html-webpack-plugin" ) const path = require("path" ) const webpack = require("webpack" ) module.exports = { resolve: { extensions: [".js" , ".jsx" , ".json" ] }, entry: path.resolve(__dirname, "src/index.jsx" ), module: { rules: [ { test: /\.jsx?/, exclude: /node_modules/, use: { loader: "babel-loader" , options: { babelrc: false , presets: [ require.resolve("@babel/preset-react" ), [require.resolve("@babel/preset-env" ,{module:false })] ] } } } ] }, devServer: { hot: true }, plugins: [ new HtmlWebPackPlugin({ template: path.resolve(__dirname, "src/index.html" ), filename: "index.html" }), new webpack.HotModuleReplacementPlugin() ] }
作用域 全局变量是挂在的在全局对象
上的
在windows的全局对象是windows
在Mac的全局对象是brower
我们在页面上引入的script文件是全局的,因此文件里面的变量也是全局的,这样就有可能造成作用域被污染,变量发生冲突
1 2 3 <script src ="/.moduleA.js" > </script > <script src ="/.moduleB.js" > </script > <script src ="/.moduleC.js" > </script >
这时可以考虑使用闭包
1 2 3 4 5 6 7 8 9 10 11 12 var moduleA= (function ( ) { var name = "susan" var sex = "nv" return { tell : function ( ) { console .log("my name is " ,name) } } })()
第二种写法:
1 2 3 4 5 6 7 8 (function ( ) { var name = "susan" var sex ="nv" function tell ( ) { console .log("my name is " ,name) } window .susanModule = { tell } })(window )
模块导出机制的演变历史 AMD asynchronous Module Definition
:异步模块定义
1 2 3 4 5 6 7 8 define("getSum" , ["math" ], function (math ) { return function (a, b ) { console .log("sum:" , math.sum(a,b)) } })
CommonJS 1 2 3 4 5 6 7 const math = require ("./math" )exports .getSum = function (a, b ) { return a + b }
ES6 Module 1 2 3 4 5 6 7 import math from "./math" export function sum (a, b ) { return a + b }
快速入门 开始前的准备 第一步安装webpack 首先我们可以通过npm全局安装一下webpack,
1 2 //全局安装 npm install -g webpack
接着我们新建一个文件夹,命名为:webpack-demo。
将终端切换到webpack-demo下执行下面的语句,将webpack安装到webpack-demo目录下。
1 npm install --save-dev webpack
第二步创建package.json文件 通过终端进入webpack-demo
目录下,然后执行npm init
命令,终端会咨询你一些npm的配置,例如项目名称、作者等。在这里,我们仅仅为了演示和学习,一路回车即可。
这时候我们就自动生成了一个webpack.json的文件,我们可以通过这个文件看到当前的自动化脚本和项目的依赖等信息。
第三步编写webpack.config.js 在编写webpack.config.js
之前,为了方便学习演示,我们先创建几个文件夹:
build-用来存放打包后的资源
html-用来存放html文件
js-用来存放js文件
我们在html文件夹下,创建一个index.html文件,在这个文件中我们要引用稍后通过webpack打包好的js资源,并进行效果的展示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <meta http-equiv ="X-UA-Compatible" content ="ie=edge" > <title > Hello Webpack</title > </head > <body > <div id ="root" > </div > <script type ="text/javascript" src ="../build/bundle.js" > </script > </body > </html >
这里面我们在body中编写了一个id为root的div,稍后我们会通过js代码,在root下添一个div标签,其内容是:Hello Webpack!
。
紧接着,我们引入了一个script标签,它的资源引用指向build文件夹下的bundle.js文件(我们会在webpack输出配置中配置输出的文件名,这个稍后就会讲)。
接着我们在js文件夹下面新建一个index.js文件:
1 2 3 4 5 var domDiv=document .createElement("div" );domDiv.textContent="Hello Webpack!" var root=document .querySelector("#root" );root.appendChild(domDiv);
在这里,我们在id为root的标签下,添加了一个内容为:Hello Webpack!
的div标签。到这里,我们终于把前期工作都准备完毕了。
我们在webpack-demo
的文件夹下,创建webpack.config.js
的文件,
1 2 3 4 5 6 7 8 9 10 11 module .exports={ entry :__dirname+"/js/index.js" , output :{ path :__dirname+"/build" , filename :"bundle.js" } }
在这里通过CommonJS语法规范,导出了一个对象。
开始打包 完成了最简单的webpack配置,接着我们在终端输入webpack
进行打包。
在build文件下面多了一个bundle.js文件,即webpack替我们打包后生产的文件。
然后我们在浏览器中打开index.html
即可发现我们浏览器中显示了:Hello Webpack!
多入口文件打包 有时候我们的入口文件可能不止一个,例如我们在js目录下,再新建一个greeter.js
的文件。
1 2 3 4 5 var domGreet=document .createElement("div" )domGreet.textContent="Welcome to learn Webpack" ; var root=document .querySelector("#root" );root.appendChild(domGreet);
依旧是在root下增加一个子元素,子元素的内容为Welcome to learn Webpack
。
接着我们修改index.html
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <meta http-equiv ="X-UA-Compatible" content ="ie=edge" > <title > Hello Webpack</title > </head > <body > <div id ="root" > </div > <script type ="text/javascript" src ="../build/bundle.js" > </script > <script type ="text/javascript" src ="../build/greeter.js" > </script > </body > </html >
我们在index.html
中多引入了一个名为greeter.js
的资源。
接着,我们修改webpack.config.js
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 module .exports={ entry :{ bundle :__dirname+"/js/index.js" , greeter :__dirname+"/js/greeter.js" }, output :{ path :__dirname+"/build" , filename :"[name].js" } }
这时候entry的值是一个对象,通过属性和值的方式定义多个入口文件,我们在output
的filename
值中引用[name]
,就会把entry中的属性名作为文件的名字,打包输出。
这时候我们的build文件下就会存在两个打包后的js文件:bundle.js
和greeter.js
文件。
这时候我们再打开index.html
文件
Loader
Loader主要是用来对文件资源进行转换打包,
例如:我们js中运用了es6的语法,而有的浏览器不支持,我们就可以用babel将es6的语法转换成浏览器支持的es5语法。
在这里我们主要通过运用url-loader对图片资源进行打包,演示一下loader的作用。
新建一个文件夹,命名为image。将图片放在这个目录下。
我们修改index.js文件,新建一个img
标签,显示该图片,并将标签加到id为root
的div下。
1 2 3 4 5 6 7 8 var domDiv=document .createElement("div" );domDiv.textContent="Hello Webpack!" var root=document .querySelector("#root" );root.appendChild(domDiv); var image=document .createElement("img" );image.src=require ('../image/pic01.jpg' ); root.appendChild(image)
接着我们修改我们的webpack.config.js
文件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 module .exports={ entry :{ bundle :__dirname+"/js/index.js" , greeter :__dirname+"/js/greeter.js" }, output :{ path :__dirname+"/build" , filename :"[name].js" , publicPath :"./" }, module :{ rules :[ { test : /\.(png|jpg)?$/ , use:[ { loader :"url-loader" , } ] }, ] } }
在这里,我们增加了module
,在这里我们在rules
数组中添加我们的规则,例如在这里我们添加了一个规则:
在遇到png或jpg结尾的文件后缀时,我们loader运用url-loader
去转换。
好了,写完这个后,我们接着利用npm去安装一下url-loader
,我们可以修改packge.json
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 { "name" : "webpack-demo" , "version" : "1.0.0" , "description" : "" , "main" : "index.js" , "scripts" : { "test" : "echo \"Error: no test specified\" && exit 1" }, "author" : "" , "license" : "ISC" , "devDependencies" : { "webpack" : "^4.16.2" , "webpack-cli" : "^3.1.0" , "url-loader" :"1.0.1" } }
然后在终端运行npm install
,npm就会自动帮我安装url-loader
。接着我们通过webpack
命令进行打包。
打包完成后,我们运行index.html
文件,我们可以看到图片就被加载到页面中了
Plugins
Plugins我们可以通过它,给项目配置很多的插件。
webpack也为我提供了完整的插件文档:
在这里我们主要使用BannerPlugin给每个打包后的js文件添加banner文字头,来举例学习Plugins的作用。
根据文档我们发现使用BannerPlugin很简单,我们修改webpack.config.js文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 module .exports={ entry :{ bundle :__dirname+"/js/index.js" , greeter :__dirname+"/js/greeter.js" }, output :{ path :__dirname+"/build" , filename :"[name].js" , publicPath :"./" }, module :{ rules :[ { test : /\.(png|jpg)$/ , use:[ { loader :"url-loader" , options :{ limit :8194 , publicPath :"../build" } } ] }, ] }, plugins : [ new webpack.BannerPlugin('大麦所有,盗版必究' ) ] }
我们添加了plugins
属性,该属性的值是一个数组,我们可以在该数组中不断的new 插件的实例对象,通过这样我们就可以改webpack设置插件。
是不是很简单?接着我们打包文件,在终端输入webpack
打包。
查看我们的打包后的文件,以bundle.js为例:
1 2 !function (e ) {var t={};function n (r ) {if (t[r])return t[r].exports;var o=t[r]={i :r,l :!1 ,exports :{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0 ,o.exports}n.m=e,n.c=t,n.d=function (e,t,r ) {n.o(e,t)||Object .defineProperty(e,t,{enumerable :!0 ,get :r})},n.r=function (e ) {"undefined" !=typeof Symbol &&Symbol .toStringTag&&Object .defineProperty(e,Symbol .toStringTag,{value :"Module" }),Object .defineProperty(e,"__esModule" ,{value :!0 })},n.t=function (e,t ) {if (1 &t&&(e=n(e)),8 &t)return e;if (4 &t&&"object" ==typeof e&&e&&e.__esModule)return e;var r=Object .create(null );if (n.r(r),Object .defineProperty(r,"default" ,{enumerable :!0 ,value :e}),2 &t&&"string" !=typeof e)for (var o in e)n.d(r,o,function (t ) {return e[t]}.bind(null ,o));return r},n.n=function (e ) {var t=e&&e.__esModule?function ( ) {return e.default}:function ( ) {return e};return n.d(t,"a" ,t),t},n.o=function (e,t ) {return Object .prototype.hasOwnProperty.call(e,t)},n.p="./" ,n(n.s=0 )}([function (e,t,n ) {var r=document .createElement("div" );r.textContent="Hello Webpack!" ;var o=document .querySelector("#root" );o.appendChild(r);var u=document .createElement("img" );u.src=n(1 ),o.appendChild(u)},function (e,t ) {e.exports="../build/a2d8df5678e4b3f4b3d686a173544500.jpg" }]);
我们可以看到我们的打包后的文件头部就被添加了文字Banner。
模块
Webpack的打包逻辑
从入口文件开始,分析整个应用的依赖树
将每个依赖模块包装起来,放到一个数组中 等待调用
实现模块加载方法,并把它放到模块执行的环境中,确保模块间可以互相调用
把执行入口文件的逻辑放在一个函数表达式中,并立即执行这个函数
npm与包管理器
1 2 3 4 5 6 7 8 9 10 11 12 { "name" :"demo" , "version" :"1.1.0" , "description" :"" , "main" :"index.js" "scripts" : { "test" :"each \"Error :no test spified\" && exit 1" }, "author" :"" , "license" :"ISC" }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 # 设置淘宝镜像仓库 npm config set registry https://npm.taobao.org # 安装 # save的功能:自动将下载的包写入package.json的dependencies字段 npm install lodash -s # or npm install lodash --save # 写入package.json的devDependencies字段 npm install lodash --save-dev # 默认-s参数,所以会同时写入dependencies字段和devDependencies字段 # 如果想写入一个的话,使用only npm insall --only=dev # rm -rf node_modules && npm install
版本语义化
1.2.3:大版本为1,中版本为2,小版本为3
^version
:中版本中最新的版本
^1.0.1
:对于所有形为1.x.x
版本中,1.0.1
是最新的版本
~version
:小版本中最新的版本
~1.0.1
:对于所有形为1.0.x
版本中,1.0.1
是最新的版本
npm install的过程
寻找包版本信息文件(package.json
),依照他进行安装
查package.json中的依赖,并检查项目中其他的版本信息文件
如果发现了新包,就更新版本信息文件
模块系统
当今越来越多的网站已经从网页模式进化到了 Webapp 模式。
webapp通常是一个单页面应用,每一个视图通过异步的方式加载 ,这导致页面初始化和使用过程中会加载越来越多的 JavaScript 代码,这给前端开发的流程和资源组织带来了巨大的挑战。
在开发环境组织好这些碎片化的代码和资源,并且保证他们在浏览器端快速、优雅的加载和更新,就需要一个模块化系统。
webpack默认的入口就是src/indesx.js
,默认的出口文件就是dist/main.js
如果想要修改的话,就需要修改webpack.config.js
1 2 3 4 5 6 7 8 9 10 11 12 13 const path = require ("path" )module .exports = { entry : "./app.js" , output : { path : path.join(__dirname, "dist" ), filename : "bundle.js" }, devServer : { port : 3000 , pubilcPath : "/dist" } }
webpack-dev-server 监听工程目录的改动,动态的打包和更新
webpack中一切皆资源 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 const path = require ("path" )module .exports = { entry : "./src/app.js" , output : { path : path.join(__dirname, "dist" ), filename : "bundle.js" }, module :{ rules : [ { test : /\.css$/ , use: [ "style-loader" , "css-loader" ] } ] } }
1 2 3 4 import "./style/mycss.css" console .log("1232" )document .write("123123213asdasda" )
1 2 3 4 5 6 7 8 9 10 11 12 13 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <meta http-equiv ="X-UA-Compatible" content ="ie=edge" > <title > Hello Webpack</title > </head > <body > <div id ="root" > </div > <script type ="text/javascript" src ="../dist/bundle.js" > </script > </body > </html >
webpack构建react工程 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 npm init -y npm install react react-dom npm install redux react-redux npm install webpack webpack-cli -D # 核心包和命令行包 npm install @babel/core @babel/cli # bebel的转换规则(preset-env负责ES版本的转换,preset-react负责解析jsx) npm install @babel/preset-env @babel/preset-react # plugin-proposal-class-properties 负责解析static PropsType的静态写法 npm install @babel/plugin-proposal-class-properties npm install babel-loader npm install html-webpack-plugin -d
创建src
目录,在目录里创建App.jsx
1 2 3 4 5 6 7 8 9 10 11 12 13 import React from "react" import ReactDom from "react-dom" const App = () => { return <div > <h1 > hello world</h1 > </div > } export default AppReactDom.render(<App > </App > ,document .getElementById("root" ))
创建index.html
1 2 3 4 5 6 7 8 9 10 11 12 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <meta http-equiv ="X-UA-Compatible" content ="ie=edge" > <title > Hello Webpack</title > </head > <body > <div id ="root" > </div > </body > </html >
创建index.jsx
:
1 2 3 4 5 6 7 8 9 import App from "./App" if (module .hot) { module .hot.accept(err => { if (err) { console .log("热替换出错" ) } }) }
创建babel的配置
方案一:在package.json
里面填写
1 2 3 4 5 6 7 8 9 { "name" : "reactdomo" , "version" : "1.0.0" , "description" : "" , ... "babel" : { "presets" : ["@babel/presets-env" ] } }
方案二:创建.babelrc
文件(优先级比较高)
1 2 3 { "preset": ["@babel/presets-env"] }
编写webpack.config.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 const HtmlWebPackPlugin = require ("html-webpack-plugin" )const path = require ("path" )const webpack = require ("webpack" )module .exports = { resolve : { extensions : [".js" , ".jsx" , ".json" ] }, entry : path.resolve(__dirname, "src/index.jsx" ), module : { rules : [ { test : /\.jsx?/ , exclude: /node_modules/ , use: { loader : "babel-loader" , options : { babelrc : false , presets : [ require .resolve("@babel/preset-react" ), [require .resolve("@babel/preset-env" ,{module :false })] ] } } } ] }, devServer : { hot : true }, plugins : [ new HtmlWebPackPlugin({ template : path.resolve(__dirname, "src/index.html" ), filename : "index.html" }), new webpack.HotModuleReplacementPlugin() ] }
执行webpack --mode devlepoment
, 执行webpack-dev-server --open
或者:直接填写package.json
,然后npm install
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 { "name" : "todolist_demo" , "version" : "1.0.0" , "description" : "" , "main" : "index.js" , "scripts" : { "test" : "echo \"Error: no test specified\" && exit 1" }, "keywords" : [], "author" : "" , "license" : "ISC" , "dependencies" : { "@babel/cli" : "^7.10.3" , "@babel/core" : "^7.10.5" , "@babel/preset-env" : "^7.10.4" , "@babel/preset-react" : "^7.10.4" , "babel-loader" : "^8.1.0" , "html-webpack-plugin" : "^4.3.0" , "react" : "^16.13.1" , "react-dom" : "^16.13.1" , "react-redux" : "^7.2.1" , "redux" : "^4.0.5" }, "devDependencies" : { "@babel/plugin-proposal-class-properties" : "^7.10.4" , "webpack" : "^4.44.0" , "webpack-cli" : "^3.3.12" } }
reference