[前端工具]Webpack2 手把手一起入門
前言
其實筆者一直都有在學習使用webpack,不過一直都沒有摸得很深,除了前陣子要做react的demo有從無到有自己整個做出來之外,其他的狀況之下,angularjs筆者使用的是gulp做整合,vue and angular4都有cli,不過其實真的要用在大專案或多人協作的狀況下,客製化的狀況就會越來越多,所以乾脆下定決心,把整個webpack 2摸透徹一點,一切從入門開始,並且把這些心得筆記下來,希望同樣會幫助到一些不太懂webpack 2的人,但其實webpack 2的細節非常的多,所以一篇文章想要把較常用的全部解釋完畢,可能連筆者自己以後回頭看都沒耐心看完了,所以打算分幾篇來解釋webpack2,因為網路上google到的大部份都是webpack1的文章,所以很多照著參考做出來都已經跑不起來了,所以希望這次紀錄下來的webpack2會幫助到多數的人。
導覽
在此筆者不想多介紹npm了,如果連npm都不懂的話,這篇可能不是那麼適合你,就把webpack的整合交給其他人做,或者先了解一下npm怎麼使用和安裝,再回來看一下怎麼學習webpack,其實webpack本身就是node.js和純javascript的整合,包括了一些webpack的plugin配置,首先開啟一個新專案,打上npm init建立預設的package.json,接著就安裝webpack,還有live-server以方便把網站跑起來。
npm install --g live-server
npm install --save-dev webpack
安裝完之後應該可以看到package.json,在devDependencies就安裝了webpack了,到這邊我們其實就已經安裝成功了,接著新增一支預設的index.html,還有script資料夾和webpack.config.js,最後筆者的目錄會是長這個樣子的
先看一下webpack.config.js最基本的部份
const path = require('path'), //node.js提供的
webpack = require('webpack');
const webpackConfig = {
entry: {//進入點
app: [
'./src/index.js',//打包後會是app.js
]
},
output: {//輸出
path: path.join(__dirname, 'dist'),//絕對路徑,輸出為dist目錄
filename: '[name].js',//輸出的就是上面的app檔名
publicPath: '/' //使用在html或url-loader會參考的圖片的路徑
},
resolve: {
extensions: ['.js'] //import的時候,就可以忽略js了
}
}
module.exports = webpackConfig
其實幾乎所有的webpack都是這樣子寫的,但是其實我們最後webpack會吃的是這個module,在最下面會export出去,所以這樣子的結構我們可以完全拆開來,最後輸出一個對象就可以了,比如下面的格式
const path = require('path'), //node.js提供的
webpack = require('webpack');
const webpackConfig = {
entry: {},
output: {},
resolve: {}
}
webpackConfig.entry = {//進入點
app: [
'./src/index.js',//打包後會是app.js
]
}
webpackConfig.output = {//輸出
path: path.join(__dirname, 'dist'),//絕對路徑,輸出為dist目錄
filename: '[name].js',//輸出的就是上面的app檔名
publicPath: '/' //會參考比如圖片的路徑
}
webpackConfig.resolve = {
extensions: ['.js'] //import的時候,就可以忽略js了
}
module.exports = webpackConfig
當然我們也可以在最後針對這個物件的值做任何變化
const path = require('path'),
webpack = require('webpack');
const webpackConfig = {
entry: {
app: [
'./src/index.js',
]
},
output: {
path: path.join(__dirname, 'dist'),
filename: '[name].js',
publicPath: '/'
},
resolve: {
extensions: ['.js']
}
}
webpackConfig.entry.app.push('./src/index1.js') //針對entry多新增一支js檔,一起打包
module.exports = webpackConfig
接著看一下index.html的部份
<!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>Document</title>
</head>
<body>
<script src="./dist/app.js"></script>
</body>
</html>
js部份
index1.js
----------------
export default {
print() {
console.log('print')
}
}
index.js
----------------
import index1 from './index1'
index1.print();
console.log('index');
然後我們可以在package.json先加上一個監控webpack的動作
"scripts": {
"dev": "webpack --watch"
}
接著可以在cmd打npm run dev執行完成之後,再輸入live-server打開網頁,主控台應該就可以看到起手式成功了。
大致上有三種打包的方式,一種就是spa的方式,監聽入口點,然後其餘的有import的話就會自動依賴進來。
entry: {
app: [
'./src/index.js',
]
}
另一種方式則是把所有檔案打包成一份,比如說我們想要想要打包後,放在現有的專案共用layout裡面,這也是我們比較常看到的bundle方式,但是我們使用webpack的方式,就可以使用es6或typescript的方式來開發和打包,
entry: {
app: [
'./src/index.js',
'./src/home.js',
'./src/index-outside.js'
]
}
最後一種就是我們每個頁面都希望有自己獨立的js檔,這樣子我們就可以在每個頁面只放進此頁面需要使用到的js檔,而此使用情境一樣是可以使用在現有的網頁裡面。
entry: {
app: './src/index.js',
home: './src/index1.js',
indexOutside: './src/index-outside.js'
}
從上面三個例子應該可以明顯看得出來,其實你只要把資料結構對應好,webpack就會按照你設定的結構做打包。
當我們要使用es6的時候,我們就必須要使用babel了,但是因為babel也是一個很大的議題,所以之後等筆者有時間深入研究了,再來紀錄此類的相關議題,先安裝一下關於babel一些相關的package
npm i babel-core babel-loader babel-plugin-transform-runtime babel-preset-env babel-preset-stage-2 --D
接著新增.babelrc的檔案,這個是屬於babel的配置。
{
"presets": [
["env", { "modules": false }],
"stage-2"
],
"plugins": ["transform-runtime"],
"comments": false
}
接著就是在webpack.config.js的部份,加入modules和loader的設定
const path = require('path'),
webpack = require('webpack');
const webpackConfig = {
entry: {
app: './src/index.js',
home: './src/index1.js',
indexOutside: './src/index-outside.js'
},
output: {
path: path.join(__dirname, 'dist'),
filename: '[name].js',
publicPath: '/'
},
resolve: {
extensions: ['.js'],
},
module: { //新增的modules部份,載入的js檔就會經過babel轉譯了
rules: [
{
test: /\.js$/,
loader: 'babel-loader',
include: [path.join(__dirname,'src')]
}
]
}
}
module.exports = webpackConfig
如果想真的測試babel是否有生效的話,可以測試一些chrome還不支援的語言特性,比如spread,或者是async await的部份,如果我們想要在js裡面可以import css或scss的話,也就是都要下載相關的loader。
npm install css-loader style-loader sass-loader node-sass --D
webpack.config的部份
module: {
rules: [
{
test: /\.js$/,
loader: 'babel-loader',
include: [path.join(__dirname, 'src')]
},
{//新增的css部份
test: /\.css$/,
use: [
{ loader: "style-loader" },
{ loader: "css-loader" }
]
}
]
}
其實devtool也就是在此設定sourcemap,在webpack有很多種sourcemap的方式,筆者是直接參考vue的方式,開發階段可以使用"#cheap-module-eval-source-map",正式佈署打包壓縮的時候,使用的則是最完整的"#source-map",設定方式也很簡單,可以見如下的webpack設置
devtool: '#cheap-module-eval-source-map',
在webpack的plugins有非常多的plugins可以安裝並使用,在此介紹一下uglifyjs-webpack-plugin的部份,就是壓縮js的部份,可以再module的底下再加上plugins的部份
plugins: [
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false,
drop_console: true,
},
sourceMap: true
})
]
這樣子當我們執行webpack的時候,就會壓縮並幫我們加上sourcemap,以方便偵錯了,可以注意我下面的操作,app.js就是壓縮後的檔案,另外可以切換去webpack看最原始的程式碼以便抓問題。
如果有去看過webpack相關文章或cli的設定的人,想必也會常看到process.env.NODE_ENV,這個參數其實我們是可以去設定的,一個比較簡單的環境可能有開發和產品環境,但更複雜的公司光是從開發要佈署到產品階段,至少就會有三個甚至更多的環境,比如說dev->qat->production這個流程,每個階段的設定可能都不太一樣,以目前這篇的範例來說,我是在開發階段就已經加上uglifyjs了,但是我希望的卻是只有要發佈到正式環境的時候,才會需要uglifyjs,而要設定NODE_DEV其實也很簡單,就是當我們要下cmd的指令同時去設定就可以了,比如說當我想使用webpack --watch的時候,是要跑dev的環境,但是當我們跑webpack的時候,想要跑的是prod的環境,首先我們來修改一下package.json的檔案
{
"name": "webpack-example",
"version": "1.0.0",
"description": "",
"main": "webpack.config.js",
"scripts": {
"dev": "set NODE_ENV=dev && webpack --watch",
"prod": "set NODE_ENV=prod && webpack"
},
"author": "",
"license": "ISC",
"devDependencies": {
"@types/webpack": "^2.2.15",
"babel-core": "^6.25.0",
"babel-loader": "^7.0.0",
"babel-plugin-transform-runtime": "^6.23.0",
"babel-preset-env": "^1.5.2",
"babel-preset-stage-2": "^6.24.1",
"css-loader": "^0.28.4",
"node-sass": "^4.5.3",
"sass-loader": "^6.0.5",
"style-loader": "^0.18.2",
"webpack": "^2.6.1"
}
}
webpack.config.js
接著我們就可以在webpack.config.js裡面判斷環境做出對應處理的事情
const path = require('path'),
webpack = require('webpack');
const webpackConfig = {
entry: {
app: './src/index.js'
},
output: {
path: path.join(__dirname, 'dist'),
filename: '[name].js',
publicPath: '/'
},
resolve: {
extensions: ['.js'],
},
devtool: '',
module: {
rules: [
{
test: /\.js$/,
loader: 'babel-loader',
include: [path.join(__dirname, 'src')]
},
{//新增的css部份
test: /\.css$/,
use: [
{ loader: "style-loader" },
{ loader: "css-loader" }
]
},
{
test: /\.scss$/,
use: [{
loader: "style-loader"
}, {
loader: "css-loader"
}, {
loader: "sass-loader"
}]
}
]
},
plugins: []
};
switch (process.env.NODE_ENV.trim()) { //從這邊判斷環境,以做出相對應的webpack修改
case "dev":
webpackConfig.devtool = '#cheap-module-eval-source-map';
break;
case "prod":
webpackConfig.devtool = '#source-map';
webpackConfig.plugins.push(
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false,
drop_console: true,
},
sourceMap: true
})
);
break;
}
module.exports = webpackConfig
當我們要發佈的時候,我們通常都會希望把整個folder刪除掉,並且重新建立,否則如果我們的檔名有hash的話,就會一直產生新的檔案,即然我們已經安裝了npm,想當然的我們就也會有node.js的功能,我們也可以另外用npm來安裝node.js的功能,在node裡面有一個rimraf內建的package,我們就直接require,並直接使用就行了
const path = require('path'),
rimraf = require('rimraf'),
webpack = require('webpack');
const webpackConfig = {
entry: {
app: './src/index.js'
},
output: {
path: path.join(__dirname, 'dist'),
filename: '[name].js',
publicPath: '/'
},
resolve: {
extensions: ['.js'],
},
devtool: '',
module: {
rules: [
{
test: /\.js$/,
loader: 'babel-loader',
include: [path.join(__dirname, 'src')]
},
{//新增的css部份
test: /\.css$/,
use: [
{ loader: "style-loader" },
{ loader: "css-loader" }
]
},
{
test: /\.scss$/,
use: [{
loader: "style-loader"
}, {
loader: "css-loader"
}, {
loader: "sass-loader"
}]
}
]
},
plugins: []
};
switch (process.env.NODE_ENV.trim()) {
case "dev":
webpackConfig.devtool = '#cheap-module-eval-source-map';
break;
case "prod":
rimraf(path.join(__dirname, 'dist'), () => console.log('success remove')); //當要發佈產品的時候,先把原有folder刪除
webpackConfig.devtool = '#source-map';
webpackConfig.plugins.push(
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false,
drop_console: true,
},
sourceMap: true
})
);
break;
}
module.exports = webpackConfig
webpack的設置非常多,第三方的plugin和loader也非常的多,所以根本很難講得完,希望在接下來會有一系列的研究心得分享出來,但因為現有前端框架大部份都會有現成的cli使用,所以很多時候我們並不需要學得那麼深入,但是學得深入點對於我們要客製化,或者是舊有網站做打包處理的時候,都會很有幫助,如果這篇對您有任何幫助,或者覺得有任何錯誤的地方,再請給予指導。