webpack4打包vue前端多頁面項目

用法

項目結構以下:css

project
        |- bulid                   <!-- 這個目錄是自動生成的--> |- public |- css |- js |- page1.html <!-- 插件生成的html文件--> |- page2.html <!-- 插件生成的html文件--> ... |- public/ <!-- 存放字體、圖片、網頁模板等靜態資源--> |- src <!-- 源碼文件夾--> |- components/ |- css/ |- js/ |- page1.js <!-- 每一個頁面惟一的VUE實例,需綁定到#app--> |- page2.js <!-- 每一個頁面惟一的VUE實例,需綁定到#app--> ... |- package.json |- package-lock.json |- README.md

public文件夾存放一些靜態文件,src文件夾存放源碼。每一個頁面經過一個入口文件(page1.js,page2.js,..)生成vue實例,掛載到插件生成的html文件的#app元素上。html

安裝依賴

npm install

進入開發模式

npm run start

瀏覽器會打開 http://localhost:3000,

這時頁面一片空白,顯示 cannot get幾個字。不要慌,在url後面加上 /page1.html,回車,即可看見咱們的頁面。
這是由於我把開發服務器的主頁設置爲index.html,而本例中頁面爲 page1.html,page2.html,所以會顯示一片空白。vue

開發完成了,構建生產版本

npm run build

複製代碼這會產生一個build/文件夾,裏面的文件都通過優化,服務器響應的資源,就是來自於這個文件夾。node

介紹

webpack基礎配置

咱們的開發分爲生產環境和開發環境,所以須要有2份webpack的配置文件(可能你會想用env環境變量,而後用3目運算符根據env的值返回不一樣值。然而這種方法在webpack導出模塊的屬性中無效,我試過~~~)。這裏咱們拆分紅3個文件,其中webpack.common.js是常規的配置,在兩種環境下都會用到,webpack.dev.js和webpack.prod.js則是在2種環境下的特有配置。這裏用到 webpack-merge這個包,將公共配置和特有配置進行合成。webpack

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const VueLoaderPlugin = require('vue-loader/lib/plugin');
const devMode = process.env.NODE_ENV !=='production';
// 須要被打包入口文件數組
// 數組元素類型 {string|object}
// string:將以默認規則生成bundle
// object{filename|title|template} 生成的bundle.html的文件名|title標籤內容|路徑 /public 下的模板文件(需指定文件後綴)
const entryList = [
    'page1',
    'page2',
];


/**
 * @param {array} entryList
 * @param {object} option:可選  要手動配置的內容
 */
const createEntry = (list = [], option = {}) => {
    const obj = {};
    list.forEach((item) => {
        const name = item.filename ? `./js/${item.filename}` : `./js/${item}`;
        obj[name] = path.resolve(__dirname, './src', `./${item}.js`);
    });
    return Object.assign(obj, option);
};


module.exports = {
    entry: createEntry(entryList),
    output: {
        path: path.resolve(__dirname, './build'),
    },
    module: {
        rules: [
            {
                test: /\.js$/,
                exclude: /(node_modules|bower_components)/,
                use: {
                    loader: 'babel-loader',
                    options: {
                        presets: ['@babel/preset-env'],
                    },
                },
            },
            {
                test: /\.vue$/,
                use: 'vue-loader',
            },
            {
                test: /\.(woff|woff2|eot|ttf|otf)$/,
                use: {
                    loader: 'file-loader',
                    options: {
                        name: 'public/fonts/[name].[ext]',
                    },
                },
            },
            {
                test: /\.(png|svg|jpg|gif)$/,
                use: {
                    loader: 'file-loader',
                    options: {
                        name: 'public/images/[name].[ext]',
                    },
                },
            },
        ],
    },
    plugins: createPluginInstance(entryList).concat([
        // vue SFCs單文件支持
        new VueLoaderPlugin(),
    ]),
};

這裏咱們沒有進行css文件的配置,是由於生產環境下須要優化、提取,因此在另外2個文件分別配置。web

const webpack = require('webpack');
const path = require('path');
const merge = require('webpack-merge');
const common = require('./webpack.common.js');

module.exports = merge(common, {
    mode: 'development',
    devtool: 'inline-source-map',
    output: {
        filename: '[name].js',
        chunkFilename: '[name].js',
    },
    module: {
        rules: [
            {
                test: /\.(css|less)$/,
                use: [
                    'vue-style-loader',
                    'css-loader', 
                    'postcss-loader',
                    'less-loader'
                ],
            },
        ],
    },
    resolve: { alias: { vue: 'vue/dist/vue.js' } },
});

vue分爲開發版本和生產版本,最後一行是根據路徑指定使用哪一個版本。npm

const webpack = require('webpack');
const merge = require('webpack-merge');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const UglifyJsPlugin = require("uglifyjs-webpack-plugin");
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
const common = require('./webpack.common.js');

module.exports = merge(common, {
    mode: 'production',
    output: {
        filename: '[name].[contenthash].js',
        chunkFilename: '[name].[contenthash].js',
    },
    resolve: { alias: { vue: 'vue/dist/vue.min.js' } },
    module: {
        rules: [
            {
                test: /\.(css|less)$/,
                use: [
                    MiniCssExtractPlugin.loader, 
                    'css-loader', 
                    'postcss-loader',
                    'less-loader'
                ],
            },
        ],
    },

在production環境下,咱們使用了哈希值便於緩存,之後往生產環境下添加其餘資源都會如此。json

解決文件輸出目錄

咱們期待的build文件夾具備以下結構:數組

build
  |- css/ |- js/ |- page1.html |- page2.html ...

即文件按照類型放在一塊兒,html文件直接放在該目錄下,但是咱們上面的配置的輸出結果是混合在一塊兒的。因爲name屬性既能夠是文件名,也能夠是/dir/a之類帶有路徑的文件名,咱們根據這個特色作出一些修改。瀏覽器

直接對output的輸出路徑更改

好比改成build/js,其餘資源利用相對路徑好比../page1.html進行修改。我一開始就這樣作的,但最終會致使開發服務器沒法響應文件的變化,由於他只能針對輸出目錄下的文件進行監聽,該目錄之上的文件變化無能爲力。

修改入口名稱

這也是咱們的最終解決方案。將原來的文件名page1修改成/js/page1,最終輸出的js文件便都會放在js文件夾裏。在生產環境下咱們經過 MiniCssExtractPlugin 這個插件提取js文件中的css,這是該插件的配置

new MiniCssExtractPlugin({ filename:'[name].[contenthash].css' }) 複製代碼這裏的name就是當初入口的名字,受到入口名稱更改的影響,上面最終會變成 js/page1.131de8553ft82.css,而且該佔位符[name]只在編譯時有效,這意味着沒法用函數對該值進行處理。所以不能使用[name]佔位符達到想要的目的,乾脆只用[id]。 new MiniCssExtractPlugin({ filename:'/css/[id].[contenthash].css' })

代碼分割

在webpack4中使用optimization.splitChunks進行分割.

const path = require('path');

module.exports = {
   // ... 省略其餘內容
    optimization:{
        runtimeChunk:{
            name:'./js/runtime'
        },
        splitChunks:{
            // 避免過分分割,設置尺寸不小於30kb
            //cacheGroups會繼承這個值
            minSize:30000,
            cacheGroups:{
                //vue相關框架
                main:{
                    test: /[\\/]node_modules[\\/]vue[\\/]/,
                    name: './js/main',
                    chunks:'all'
                },
                //除Vue以外其餘框架
                vendors:{
                    test:/[\\/]node_modules[\\/]?!(vue)[\\/]/,
                    name: './js/vendors',
                    chunks:'all'
                },
                //業務中可複用的js
                extractedJS:{
                    test:/[\\/]src[\\/].+\.js$/,
                    name:'./js/extractedJS',
                    chunks:'all'
                }

            }
        }
    }
};

runtimeChunk包含了一些webapck的樣板文件,使得你在不改變源文件內容的狀況下打包,哈希值仍然改變,所以咱們把他單獨提取出來,點這兒瞭解更多。
cacheGroups用於提取複用的模塊,test會嘗試匹配(模塊的絕對路徑||模塊名),返回值爲true且知足條件的模塊會被分割。知足的條件可自定義,好比模塊最小應該多大尺寸、至少被導入進多少個chunk(即複用的次數)等。默認在打包前模塊不小於30kb才被會分割。

樹抖動

在package.json里加入
"sideEffects":[".css",".less","*.sass"]
複製代碼該數組以外的文件將會受到樹抖動的影響——未使用的代碼將會從export導出對象中剔除。這將大大減小無用代碼。若是樹抖動對某些文件具備反作用,就把這些文件名放進數組以跳過此操做。css文件(包括.less,.sass)都必須放進來,不然會出現樣式丟失。

插件的使用

clean-webpack-plugin

每次打包後都會生成新的文件,這可能會致使無用的舊文件堆積,對於這些無用文件本身一個個刪太麻煩,這個插件會在每次打包前自動清理。實際中,咱們不想在開發環境下清理掉build命令生成的文件,所以只在生產環境使用了這個插件。

html-Webpack-plugin

咱們的源碼目錄中並無html文件,打包後的多個html文件,就是咱們用這個插件生成的。

// ...省略上面已經出現過的內容

//每一個html須要一個插件實例
//批量生成html文件
const createPluginInstance = (list = []) => (
    list.map((item) => {
        return new HtmlWebpackPlugin({
            filename: item.filename ? `${item.filename}.html` : `${item}.html`,
            template: item.template ? `./public/${item.template}` :             './public/template.html',
            title: item.title ? item.title : item,
            chunks: [
                `./js/${item.filename ? item.filename : item}`,
                './js/extractedJS',
                './js/vendors',
                './js/main',
                './js/runtime',
                './css/styles.css',
                devMode ? './css/[id].css' : './css/[id].[contenthash].css',
            ],
        });
    })
);

默認會將全部的入口文件,代碼分割後的文件打包進一個html文件裏,經過指定chunks屬性來告訴插件只包含哪些塊,或者exludeChunks指定不該包含那些chunks。這裏有個小問題,咱們沒法讓文件恰好只包含他須要的塊。若想不包含未使用的chunks,只能根據實際狀況手動配置,用這個函數批量生成的文件,總會包含全部的公共打包文件。

mini-css-extract-plugin (prooduction)

該插件用於提取js文件中的css到單獨的css文件中。

//...省略其餘內容
plugins:[
        new CleanWebpackPlugin('build'), 
        // 提取css
        new MiniCssExtractPlugin({
            filename:'./css/[id].[contenthash].css'
        }),
        //優化緩存
        new webpack.HashedModuleIdsPlugin()
    ]   
## optimize-css-assets-webpack-plugin (production)
用於精簡打包後的css代碼,設置在配置optimization的minimizer屬性中,這將會覆蓋webpack默認設置,所以也要同時設置js的精簡工具(這裏咱們用uglifyplugin插件):
optimization: {
        minimizer:[
          new UglifyJsPlugin({
            cache: true,
            parallel: true
          }),
          new OptimizeCSSAssetsPlugin()
        ]
    }
## 開發服務器、熱模塊替換 (development)
webpack.dev.js中增長以下內容便可:
//...省略其餘內容
devServer:{
        index:'index.html',
        hot:true,
        contentBase:path.resolve(__dirname,'./build'),
        port:3000,
        noInfo:true
    },
plugins:[
        new webpack.HotModuleReplacementPlugin()
    ]

使用開發服務器能夠在咱們修改了源文件後自動刷新,由於是將數據放在內存中,所以不會影響硬盤中build文件夾。熱模塊替換還須要在源文件作相應修改。咱們也爲動態導入語法進行了相應配置。

其餘

public用於存放靜態資源,打包後也會在build/下建立一個同名文件夾,裏面存放的是public會被使用到的資源。若是在.css文件裏引用了public裏的資源,如圖片,添加url的時候要使用絕對路徑:

<!-- src/css/page1.css --> .bg-img { background-image:url(/public/images/1.jpg) }

複製代碼這樣經過 http/https 打開的時候就能正常使用,若是是以文件形式打開(好比打包後雙擊page1.html),會發現瀏覽器顯示沒法找到資源。經過導入圖片做爲變量引用(import name from path),既可以使用絕對路徑,也可以使用相對路徑。

相關文章
相關標籤/搜索