以前一直用的脚手架,此次本身搭建webpack前端项目,花费了很多心思,因而作个总结。javascript
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元素上。css
$ npm install
复制代码
$ npm run start
复制代码
浏览器会打开 http://localhost:3000
,这时页面一片空白,显示 cannot get几个字。不要慌,在url后面加上 /page1.html
,回车,即可看见咱们的页面。 这是由于我把开发服务器的主页设置为index.html
,而本例中页面为 page1.html,page2.html,所以会显示一片空白。html
$ npm run build
复制代码
这会产生一个build/文件夹,里面的文件都通过优化,服务器响应的资源,就是来自于这个文件夹。前端
咱们的开发分为生产环境和开发环境,所以须要有2份webpack的配置文件(可能你会想用env环境变量,而后用3目运算符根据env的值返回不一样值。然而这种方法在webpack导出模块的属性中无效,我试过~~~)。这里咱们拆分红3个文件,其中webpack.common.js
是常规的配置,在两种环境下都会用到,webpack.dev.js
和webpack.prod.js
则是在2种环境下的特有配置。这里用到 webpack-merge
这个包,将公共配置和特有配置进行合成。vue
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个文件分别配置。java
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分为开发版本和生产版本,最后一行是根据路径指定使用哪一个版本。node
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环境下,咱们使用了哈希值便于缓存,之后往生产环境下添加其余资源都会如此。webpack
咱们期待的build文件夹具备以下结构:git
build
|- css/
|- js/
|- page1.html
|- page2.html
...
复制代码
即文件按照类型放在一块儿,html文件直接放在该目录下,但是咱们上面的配置的输出结果是混合在一块儿的。因为name属性既能够是文件名,也能够是/dir/a
之类带有路径的文件名,咱们根据这个特色作出一些修改。github
好比改成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进行分割.
//webpack.common.js
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)都必须放进来,不然会出现样式丢失。
每次打包后都会生成新的文件,这可能会致使无用的旧文件堆积,对于这些无用文件本身一个个删太麻烦,这个插件会在每次打包前自动清理。实际中,咱们不想在开发环境下清理掉build命令生成的文件,所以只在生产环境使用了这个插件。
咱们的源码目录中并无html文件,打包后的多个html文件,就是咱们用这个插件生成的。
//webpack.common.js
// ...省略上面已经出现过的内容
//每一个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,只能根据实际状况手动配置,用这个函数批量生成的文件,总会包含全部的公共打包文件。
该插件用于提取js文件中的css到单独的css文件中。
//webpack.prod.js
//...省略其余内容
plugins:[
new CleanWebpackPlugin('build'),
// 提取css
new MiniCssExtractPlugin({
filename:'./css/[id].[contenthash].css'
}),
//优化缓存
new webpack.HashedModuleIdsPlugin()
]
复制代码
用于精简打包后的css代码,设置在配置optimization的minimizer属性中,这将会覆盖webpack默认设置,所以也要同时设置js的精简工具(这里咱们用uglifyplugin插件):
optimization: {
minimizer:[
new UglifyJsPlugin({
cache: true,
parallel: true
}),
new OptimizeCSSAssetsPlugin()
]
}
复制代码
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
),既可以使用绝对路径,也可以使用相对路径。