webpack官網:https://www.webpackjs.com/
准備
這時,你將看到一個空文件夾
開始
- 命令行打開至根目錄
- 鍵入 npm init,一路確定到yes ————————創建一個package.json。
安裝webpack
安裝webpack前,先附上幾個常用的npm命令
npm init 這個指令會引導你創建一個package.json,包括版本作者等信息,有助於你發包。后面安裝的包的依賴關系也會在package.json里有體現。
npm install 直接執行這個命令,會按照當前目錄下的package.json的配置去安裝各個依賴的包。
npm install [module] 在當前目錄安裝這個模塊。會去檢測該模塊是否存在於node_module文件夾中,存在了就不安裝了。
npm install [module] -g 在全局進行模塊安裝。全局模式下安裝的包,會自動注冊到系統變量 path里的。
npm install [module] --save-dev 在當前目錄下安裝這個模塊,但是僅在開發時使用。在package的"devDependencies"下,表示僅在開發的時候使用。
有一些包是需要用命令行的,這些需要注冊系統變量,因此像supervisor等包,一定要安裝在全局。否則就不能用它的命令行指令。
有一些包是在js中使用的,那么這些包安裝到當前目錄就可以了。
webpack 一般建議全局安一個,當前目錄安一個。
我們剛才已經使用了npm init創建了一個package.json,接下來我們開始安裝webpack。
首先在全局安裝一個webpack
執行 npm install webpack -g (已經全局安裝webpack 的可以跳過這一步)
然后在當前目錄下安裝一個webpack
執行 npm install webpack --save
你會發現當前目錄下新增了一個文件夾node_module,在里頭有着webpack的包
檢驗下,webpack 安裝成功了沒
執行 webpack -v
webpage-cli
這時候如果出現了
說明你安裝的是webpack 4X 后的版本,這里提示安裝 webpack-cli// 是因為到了webpack4, webpack 已經將 webpack 命令行相關的內容都遷移到 webpack-cli,所以除了 webpack 外,我們還需要安裝 webpack-cli
輸入yes之后,你會發現 再次輸入webpack -v
,它還會讓你安裝,這是因為這是在局部安裝的,我們需要全局安裝
//因為上面的webpack是全局安裝的,因此這里我們安裝weback-cli也是需要全局安裝的!
npm install --save-dev webpack-cli -g
如果webpack安裝成功了,就會在命令行打印出webpack的版本
如
使用webpack來組織文件
在直接介紹使用es6模塊化語言來組織文件之前,我們先了解一下webpack的使用。
webpack會將我們用模塊化語言語法寫成的源文件,編譯成瀏覽器可識別的文件。也就是有從源文件→線上文件的過程。
我們來實踐一下:
- 首先在根目錄下創建一個文件夾src來放源文件;
- 再創建一個文件夾dist來放編譯后文件;
- 新建一個html文件來放html文件
- 最后創建一個webpack.config.js文件。 (先創個空的,待會兒加內容)
這時你的目錄結構將如下:
webpack.config.js是webpack的配置文件。
要搞懂webpack其實就是要懂得怎么來配置 webpack.config.js。
本文介紹一個基礎的配置,完整的配置教程請參照官網文檔——webpack官網文檔。
接下來:
-
在src中新建一個文件—— sourceFile.js
文件內容,隨意點:
//sourceFile.js
console.log('我是superman');
- 配置 webpack.config.js (關鍵一步)
module.exports = {
entry:{
bundle : __dirname + '/src/sourceFile.js'
},
output:{
path: __dirname + '/dist',
filename: '[name].js'
}
}
這個文件僅有entry和output,應該是最簡單的配置文件了。
- module.exports 是CommonJS的寫法,這個是Node.js的規范
- __dirname 代表當前目錄,Node.js會去識別
- entry中,值為一個路徑,代表源文件的存放位置。鍵,則可以隨便取,在我的配置中,編譯后文件的名字就是這里的鍵。
- output中,path為編譯后的文件存放的文件夾。 filename 為編譯后文件夾名字。 其中[name]代表了entry中的鍵。也就是說上面是什么名字,編譯后就是什么名字。可以自己試驗下。
使用webpack進行編譯
在命令行鍵入 webpack -w
這時候命令行出現
這是一個警告,大概的意思就是說:沒有“mode”選項還沒有設置,webpack將返回到“production”。將“模式”選項設置為“開發”或“生產”,以啟用默認設置。那mode有什么用,怎么用?接着往下看
webpack mode
webpack的 mode
配置用於提供模式配置選項告訴webpack相應地使用其內置的優化,mode有以下三個可選值
development
開發模式production
生產模式none
不使用任何模式
配置
1. 直接寫在webpack.config.js配置中
module.exports = {
mode: 'production'
};
2. 作為webpack執行的參數
webpack --mode=production
通過上面的配置,我們就可以在業務代碼中通過process.env.NODE_ENV
拿到環境變量值,這里的process.env.NODE_ENV
要跟node的區分,這句等同於
new webpack.DefinePlugin({ "process.env.NODE_ENV": JSON.stringify("development") }),
如果我們在webpack執行命令和webpack配置文件里都沒有寫入mode配置,在執行webpack時會有如下提示:在沒有配置的情況下默認顯示production
,這里我們建議大家配置,這樣才有區分環境的意義
WARNING in configuration
The 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/concepts/mode/
使用
在開發和生產版本有很多不同之處,主要可分下面幾種
- 接口不同,后端返回的接口分線上開發
- 編譯結果不同,是否分離js,css,是否壓縮等
通過mode的設置,我們就可以輕松對開發環境做嚴格的區分
1.運用於開發和生產的接口區分
package.json
配置
{
"scripts": {
"dev": "webpack-dev-server --mode=development --devtool inline-source-map --hot",
"build":"webpack --mode=production",
},
}
接口前綴根據編譯的mode值區分
// 接口前綴配置
let baseUrl = "";
const env = process.env.NODE_ENV;
if(env === "production" || env === "none"){
baseUrl= "https://www.production.com/public/";
}else{
baseUrl= "https://www.development.com/public/";
}
export default baseUrl;
2.運用在編譯打包
這是webpack4改進很重要的一點,開發者不需要太多配置,只需要設置好mode
,webpack會根據mode在編譯打包時執行不同的操作優化,具體參考官方文檔
改進webpack4 編譯命令
這時候我們將,編譯的命令改下
#執行下面命令表示不使用任何模式
webpack --mode=none -w
執行上面的命令之后,dist中會新出現一個 bundle.js, 代碼長這個樣子:
/******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId]) {
/******/ return installedModules[moduleId].exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ i: moduleId,
/******/ l: false,
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Flag the module as loaded
/******/ module.l = true;
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/******/
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/
/******/ // define getter function for harmony exports
/******/ __webpack_require__.d = function(exports, name, getter) {
/******/ if(!__webpack_require__.o(exports, name)) {
/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter });
/******/ }
/******/ };
/******/
/******/ // define __esModule on exports
/******/ __webpack_require__.r = function(exports) {
/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/ }
/******/ Object.defineProperty(exports, '__esModule', { value: true });
/******/ };
/******/
/******/ // create a fake namespace object
/******/ // mode & 1: value is a module id, require it
/******/ // mode & 2: merge all properties of value into the ns
/******/ // mode & 4: return value when already ns object
/******/ // mode & 8|1: behave like require
/******/ __webpack_require__.t = function(value, mode) {
/******/ if(mode & 1) value = __webpack_require__(value);
/******/ if(mode & 8) return value;
/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
/******/ var ns = Object.create(null);
/******/ __webpack_require__.r(ns);
/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value });
/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
/******/ return ns;
/******/ };
/******/
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = function(module) {
/******/ var getter = module && module.__esModule ?
/******/ function getDefault() { return module['default']; } :
/******/ function getModuleExports() { return module; };
/******/ __webpack_require__.d(getter, 'a', getter);
/******/ return getter;
/******/ };
/******/
/******/ // Object.prototype.hasOwnProperty.call
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/
/******/
/******/ // Load entry module and return exports
/******/ return __webpack_require__(__webpack_require__.s = 0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, exports) {
console.log('我是superman');
/***/ })
/******/ ]);
可以看到編譯后的js多了很多額外的內容,所以如果項目小的話是不需要模塊化的。模塊化是用來構建中大型項目的。並且這是沒有指定模式的,如果切換到生產模式,會打包的更小注釋基本沒有
測試js引入
來看看效果
- 在html文件夾下新建一個 test.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>看看我們編譯后的js可不可以用</title>
</head>
<body>
<script src = "../dist/bundle.js"></script>
</body>
</html>
在瀏覽器打開test.html,你會看到瀏覽器的console中:
說明我們將源文件sourceFile.js編譯后生成的bundle.js,是可以正常使用的。
疑惑
這樣子做的話,和html中直接引用源文件效果是一樣的啊。話說為什么要編譯啊?這樣不是更麻煩嗎?
這是我剛接觸webpack的感受。后來,我逐步理解了,編譯其實是為了實現模塊化。基於AMD/CMD/CommonJS/es6的語法,瀏覽器是無法識別的。這些規范的語法,你可以感受一下:
//AMD
require(['moduleA', 'moduleB', 'moduleC'], function (moduleA, moduleB, moduleC)
{
alert('加載成功');
});
//CMD
seajs.use("../static/hello/src/main")
//CommonJS
module.export = {
name:'rouwan'
}
//es6模塊
import {module1, module2} form './module.js';
這些規范使用的語法,有的瀏覽器是不能識別的。不信你試一下,立馬報錯。好像谷歌支持的好點但也不是全部支持。因此,需要由webpack來編譯,編譯后的文件,瀏覽器能夠識別。現在,我們開始使用es6模塊語法來組織模塊吧
使用es6模塊語法
webpack可以支持es6語法。這個也是為什么webpack強大的原因。用es6a ,想想就爽。
當然,我們需要先下載配置babel
webpack 4.x 之前 babel使用
下載和配置babel
下載babel:
//下載babel的webpack加載器
npm install --save-dev babel-loader babel-core babel-preset-es2015
下載完了,要去webpack.config.js進行配置,配置完的文件如下
module.exports = {
entry:{
bundle : __dirname + '/src/sourceFile.js'
},
output:{
path: __dirname + '/dist',
filename: '[name].js'
},
module:{
loaders:[{
test: /\.js$/,
loader: 'babel?presets=es2015'
}]
}
}
可以看到,和之前的webpack.config.js相比,增加了一個loaders的配置。
大致意思是:所有的js文件,使用babel加載器來翻譯一下
具體配置原理可查官網文檔 loader的api
怎么看自己是否配置好呢?待會兒webpack編譯時看有沒有報錯,瀏覽器端有沒有識別es6語法就知道了。
測試使用es6模塊
在src文件夾下新建一個文件——moduleTest.js
//moduleTest.js
function say(){
console.log('我引用了一個模塊')
}
export {say}
將sourceFile.js的內容改為:
//sourceFile.js
import {say} from './moduleTest.js';
say();
在命令行運行webpack編譯指令
#編譯轉化
webpack --mode=none -w
如果沒有報錯,則可以接着執行,但是我出現了,這說明我的webpack是4.x之后的,4.x之前的這樣是可以的
實際上出錯信息已經說明了問題原因:
Invalid configuration object. Webpack has been initialised using a configuration object that does not match the API schema.
這一段的意思是webpack.config.js錯誤,原因是這個配置文件的版本和我們當前安裝的webpack的版本不匹配。
configuration.module has an unknown property ‘loaders’.
接下來這段我們只需要看前面一句,意思是webpack.config.js這個配置文件里的module屬性有一個未知的配置項loaders,原因嘛,就是我們當前安裝的webpack版本已經去掉了這個配置。
webpack 4.x babel使用
- 安裝
babel-loader
、@babel/core
、@babel/preset-env
npm i babel-loader @babel/core @babel/preset-env -D
webpack.config.js
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/, // 忽略node_modules里的js文件
loader: "babel-loader",
options: {
presets: ["@babel/preset-env"]
}
}
]
}
這時候的配置文件是這樣
module.exports = {
entry:{
bundle : __dirname + '/src/sourceFile.js'
},
output:{
path: __dirname + '/dist',
filename: '[name].js'
},
module: {
rules: [
{
test: /\.js$/,
// 忽略node_modules里的js文件
exclude: /node_modules/,
loader: "babel-loader",
options: {
presets: ["@babel/preset-env"]
}
}
]
}
}
- 上面的設置能夠在打包時把es6轉為es5,但不包括es6的一些新特征,比如promise
- 解決上面問題的方法是,安裝
@babel/polyfill
;@babel/polyfill
會添加很多代碼,而非按需加載。
npm i babel-loader @babel/polyfill -D
- 實現按需加載的配置
webpack.config.js
一步到位的安裝
npm i babel-loader @babel/core @babel/preset-env @babel/polyfill -D
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader",
options: {
presets: [[
"@babel/preset-env",
{
useBuiltIns: "usage"
}
]]
}
}
]
}
這時候配置文件是這樣的
webpack.config.js
module.exports = {
entry:{
bundle : __dirname + '/src/sourceFile.js'
},
output:{
path: __dirname + '/dist',
filename: '[name].js'
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader",
options: {
presets: [[
"@babel/preset-env",
{
useBuiltIns: "usage"
}
]]
}
}
]
}
}
package.json
{
"name": "npm-webpack",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"webpack": "^4.44.2"
},
"devDependencies": {
"@babel/core": "^7.11.6",
"@babel/polyfill": "^7.11.5",
"@babel/preset-env": "^7.11.5",
"babel-core": "^6.26.3",
"babel-loader": "^8.1.0",
"babel-preset-es2015": "^6.24.1",
"webpack-cli": "^3.3.12"
}
}
重新執行打包
webpack --mode=none -w
運行成功
這時候我們再打開test.html看看,
可以看到成功的執行
不支持class的問題
當我們使用了一些JavaScript的一些新特性的時候,但是有沒有在webpack.config.js里面或者是.babelrc文件中配置相關插件,就會出現
Support for the experimental syntax 'classProperties' isn't currently enabled
解決方案:安裝如下插件
npm i @babel/plugin-proposal-class-properties -D
最新的一步到位
>npm i babel-loader @babel/core @babel/preset-env @babel/polyfill @babel/plugin-proposal-class-properties -D
配置文件中添加
plugins: [
"@babel/plugin-proposal-class-properties",
]
這時候的配置文件中options選項中加載插件
module.exports = {
entry:{
bundle : __dirname + '/src/ClassAll.js'
},
output:{
path: __dirname + '/dist',
filename: '[name].js'
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader",
options: {
presets: [[
"@babel/preset-env",
{
useBuiltIns: "usage"
}
]],
plugins: [
"@babel/plugin-proposal-class-properties"
]
}
}
]
}
}
參考:
https://segmentfault.com/a/1190000006968235
https://www.webpackjs.com/concepts/mode/#用法